Concatenating a column over multiple rows in Oracle 12c - sql

I have a table of notes related to orders from an old terminal system in Oracle 12c. Each order reference has several lines of notes, ordered by a sequence number.
I want to concatenate all of the relevant notes together for each order reference so that I can try to pull some address data out of it. The address data could be spread over several different sequence numbers. The structure is:
| SEQ | NOTE_TEXT | ORDER | ... |
|-----|--------------------------|-------|-----|
| 1 | The address for this | | |
| 2 | is 123 The Street, City, | | |
| 3 | County, Postcode | | |
| 1 | This customer has ordered| | |
| 2 | this product on date | | |
| 1 | Some other note | | |
| 1 | This order is for A Smith| | |
| 2 | The address is 4 The Lane| | |
| 3 | City, County, Postcode | | |
------------------------------------------------
What I would like to turn this into is:
|--------|---------------------------------------------------------------------------|
| ORDER | NOTE_TEXT |
|--------|---------------------------------------------------------------------------|
| ABC123 | The address for this is 123 The Street, City, County, Postcode |
| DEF456 | This customer has ordered this product on date |
| GHI789 | Some other note |
| JKL012 | This order is for A Smith The address is 4 A Lane, City, County, Postcode |
|--------|---------------------------------------------------------------------------|
It would probably be good to trim each note row before concatenating but I also need to make sure that I put a space between the join of two rows, just in case someone has filled the full line with text. Oh and the sequences are out of order so I need to order by first too.
Thanks for your help!

You can use listagg for this:
select "order" || listagg(seq, '') within group (order by seq) as "order",
listagg(trim(note_text), ' ') within group (order by seq) as note_text
from your_table
group by "order";
Also, note that order is a reserved keyword in oracle. Best use some other identifier or use " to escape it.

Related

SQL Group By Query With Specific First Row

I'm using this query to pull information about companies and their scores from a ms sql database.
SELECT company, avg(score) AS Value FROM Responses where id=12 group by company
This is the result
| COMPANY | VALUE |
|: ------------ | ------:|
| Competitor A | 6.09 |
| Competitor B | 5.70 |
| Other Brand | 5.29 |
| Your Brand | 6.29 |
What I need is a query that will put one company that I will specify in the first position (in this case, the company is Your Brand) and then order the rest by the company like this.
| COMPANY | VALUE |
|: ------------ | -----:|
| Your Brand | 6.29 |
| Competitor A | 6.09 |
| Competitor B | 5.70 |
| Other Brand | 5.29 |
As #jarlh has suggested, use a CASE expression to order:
SELECT company, AVG(score) AS Value
FROM Responses
WHERE id = 12
GROUP BY company
ORDER BY CASE company WHEN 'Your Brand' THEN 0 ELSE 1 END,
AVG(score) DESC;

FIRST & LAST values in Oracle SQL

I am having trouble querying some data. The table I am trying to pull the data from is a LOG table, where I would like to see changes in the values next to each other (example below)
Table:
+-----------+----+-------------+----------+------------+
| UNIQUE_ID | ID | NAME | CITY | DATE |
+-----------+----+-------------+----------+------------+
| xa220 | 1 | John Smith | Berlin | 2020.05.01 |
| xa195 | 1 | John Smith | Berlin | 2020.03.01 |
| xa111 | 1 | John Smith | München | 2020.01.01 |
| xa106 | 2 | James Brown | Atlanta | 2018.04.04 |
| xa100 | 2 | James Brown | Boston | 2017.12.10 |
| xa76 | 3 | Emily Wolf | Shanghai | 2016.11.03 |
| xa20 | 3 | Emily Wolf | Shanghai | 2016.07.03 |
| xa15 | 3 | Emily Wolf | Tokyo | 2014.02.22 |
| xa12 | 3 | Emily Wolf | null | 2014.02.22 |
+-----------+----+-------------+----------+------------+
Desired outcome:
+----+-------------+----------+---------------+
| ID | NAME | CITY | PREVIOUS_CITY |
+----+-------------+----------+---------------+
| 1 | John Smith | Berlin | München |
| 2 | James Brown | Atlanta | Boston |
| 3 | Emily Wolf | Shanghai | Tokyo |
| 3 | Emily Wolf | Tokyo | null |
+----+-------------+----------+---------------+
I have been trying to use FIRST and LAST values, however, cannot get the desired outcome.
select distinct id,
name,
city,
first_value(city) over (partition by id order by city) as previous_city
from test
Any help is appreciated!
Thank you!
Use the LAG function to get the city for previous date and display only the rows where current city and the result of lag are different:
WITH cte AS (
SELECT t.*, LAG(CITY, 1, CITY) OVER (PARTITION BY ID ORDER BY "DATE") LAG_CITY
FROM yourTable t
)
SELECT ID, NAME, CITY, LAG_CITY AS PREVIOUS_CITY
FROM cte
WHERE
CITY <> LAG_CITY OR
CITY IS NULL AND LAG_CITY IS NOT NULL OR
CITY IS NOT NULL AND LAG_CITY IS NULL
ORDER BY
ID, "DATE" DESC;
Demo
Some comments on how LAG is being used and its values checked are warranted. We use the three parameter version of LAG here. The second parameter means the number of records to look back, which in this case is 1 (the default). The third parameter means the default value to use should a given record per ID partition be the first. In this case, we use the default as the same CITY value. This means that the first record would never appear in the result set.
For the WHERE clause above, a matching record is one for which the city and lag city are different, or for where one of the two be NULL and the other not NULL. This is the logic needed to treat a NULL city and some not NULL city value as being different.

SQL - Group two rows by columns that value and null on different columns

Question
Say I have a table with such rows:
id | country | place | last_action | second_to_last_action
----------------------------------------------------------
1 | US | 2 | reply |
1 | US | 2 | | comment
4 | DE | 5 | reply |
4 | | | | comment
What I want to do is to combine these by id, country and place so that the last_action and second_to_last_action would be on the same row
id | country | place | last_action | second_to_last_action
----------------------------------------------------------
1 | US | 2 | reply | comment
4 | DE | 5 | reply | comment
How would I approach this? I guess I would need an aggregate here but my mind is hitting completely blank on which one should I use.
It can be expected that there will always be a matching pair.
Background:
Note: this table has been derived from something like this:
id | country | place | action | time
----------------------------------------------------------
1 | US | 2 | reply | 16:15
1 | US | 2 | comment | 15:16
1 | US | 2 | view | 13:16
4 | DE | 5 | reply | 17:15
4 | DE | 5 | comment | 16:16
4 | DE | 5 | view | 14:12
Code used to partition was:
row_number() over (partition by id order by time desc) as event_no
And then I got the last and second_to_last action by getting event_no 1 & 2. So if there's more efficient way to get the last two actions in two distinct columns I would be happy to hear that.
You can fix your first data by using aggregation:
select id, country, place, max(last_action), max(second_to_last_action)
from derived
group by id, country, place;
You can do this from the original table using conditional aggregation:
select id, country, place,
max(case when seqnum = 1 then action end) as last_action,
max(case when seqnum = 2 then action end) as second_to_last_action
from (select t.*,
row_number() over (partition by id order by time desc) as seqnum
from t
) t
group by id, country, place;

PostgreSQL: Using the LEAST() command after GROUP BY to achieve first transactions

I am working with a magento table like this:
+-----------+--------------+------------+--------------+----------+-------------+
| date | email | product_id | product_type | order_id | qty_ordered |
+-----------+--------------+------------+--------------+----------+-------------+
| 2017/2/15 | x#y.com | 18W1 | custom | 12 | 1 |
+-----------+--------------+------------+--------------+----------+-------------+
| 2017/2/15 | x#y.com | 18W2 | simple | 17 | 3 |
+-----------+--------------+------------+--------------+----------+-------------+
| 2017/2/20 | z#abc.com | 22Y34 | simple | 119 | 1 |
+-----------+--------------+------------+--------------+----------+-------------+
| 2017/2/20 | z#abc.com | 22Y35 | custom | 31 | 2 |
+-----------+--------------+------------+--------------+----------+-------------+
I want to make a new view by grouping by email, and then taking the row with the LEAST of order_id only.
So my final table after doing this operation from above should look like this:
+-----------+--------------+------------+--------------+----------+-------------+
| date | email | product_id | product_type | order_id | qty_ordered |
+-----------+--------------+------------+--------------+----------+-------------+
| 2017/2/15 | x#y.com | 18W1 | custom | 17 | 1 |
+-----------+--------------+------------+--------------+----------+-------------+
| 2017/2/15 | z#abc.com | 18W2 | simple | 31 | 3 |
+-----------+--------------+------------+--------------+----------+-------------+
I'm trying to use the following query (but it's not working):
SELECT * , (SELECT DISTINCT table.email, table.order_id,
LEAST (order_id) AS first_transaction_id
FROM
table
GROUP BY
email)
FROM table;
Would really love any help with this, thank you!
I think you want distinct on:
select distinct on (email) t.*
from t
order by email, order_id;
distinct on is a Postgres extension. It takes one record for all combinations of keys in parentheses, based on the order by clause. In this case, it is one row per email, with the first one being the one with the smallest order_id (because of the order by). The keys in the select also need to be the first keys in the order by.

How to aggregate from result data Oracle SQL?

I have table :
+------+-------+-----------------+
| id | name | code | desc |
+------+-------+-----------------+
| 1 | aa | 032016 | grape |
| 1 | aa | 012016 | apple |
| 1 | aa | 032016 | grape |
| 1 | aa | 022016 | orange |
| 1 | aa | 012016 | apple |
| 1 | aa | 032016 | grape |
+------+-------+-----------------+
i tried with query:
SELECT id, name, code, desc, COUNT(code) as view
FROM mytable
GROUP BY id, name, code, desc
and the result is :
+------+-------+------------------------+
| id | name | code | desc | view |
+------+-------+------------------------+
| 1 | aa | 012016 | apple | 2 |
| 1 | aa | 022016 | orange | 1 |
| 1 | aa | 032016 | grape | 3 |
+------+-------+------------------------+
what i expected is like this :
+------+-------+----------------------------------------------------+
| id | name | code | desc | view |
+------+-------+----------------------------------------------------+
| 1 | aa | 012016,022016,032016 | apple,orange,grape | 2,1,3 |
+------+-------+----------------------------------------------------+
can anyone help me how to aggregate the result?
thanks in advance
Your table design has me a bit worried. Is it coincidence that one fruit always has the same code in the table? Then why store it redundantly? There should be a fruit table holding each fruit and its code only once. You know why this is called a relational database system, don't you?
However, with your query you are almost where you wanted to get. You have the counts per id, name, code, and desc. Now you want to aggregate even further. So in the next step group by id and name, because you want one result row per id and name it seems. Use LISTAGG to concatenate the strings in the group:
SELECT
id,
name,
listagg(code, ',') within group(order by code) as codes,
listagg(desc, ',') within group(order by code) as descs,
listagg(view, ',') within group(order by code) as views
FROM
(
SELECT id, name, code, desc, COUNT(*) as view
FROM mytable
GROUP BY id, name, code, desc
)
GROUP BY id, name
ORDER BY id, name;