Select all values (all rows) in one row Oracle - sql

I get multiple rows after executing the select-query.
But I need to get all the values of these rows in one row.
̶C̶o̶u̶n̶t̶ ̶o̶f̶ ̶r̶o̶w̶s̶ ̶i̶s̶ ̶u̶n̶k̶n̶o̶w̶n̶ ̶(̶b̶e̶f̶o̶r̶e̶ ̶t̶h̶e̶ ̶̶̶s̶e̶l̶e̶c̶t̶̶̶-̶q̶u̶e̶r̶y̶ ̶i̶s̶ ̶e̶x̶e̶c̶u̶t̶e̶d̶)̶
For example:
|----------|-----------|
| **Name** | **Value** |
|----------|-----------|
| Alex | 150 |
|----------|-----------|
| Peter | 220 |
|----------|-----------|
| Katty | 34 |
|----------|-----------|
I want to get:
|-----------|-----------|-----------|-----------|-----------|-----------|
| **Col_1** | **Col_2** | **Col_3** | **Col_4** | **Col_5** | **Col_6** |
|-----------|-----------|-----------|-----------|-----------|-----------|
| Alex | 150 | Peter | 220 | Katty | 34 |
|-----------|-----------|-----------|-----------|-----------|-----------|
Oracle 11g.
UPDATE: I realized that with an unknown number of rows, the task is difficult, so I can assume that the number of rows will be known.

To pivot over a fixed number of column, one option uses row_number() and conditional aggregation:
select
max(case when rn = 1 then name end) name1,
max(case when rn = 1 then value end) value1,
max(case when rn = 2 then name end) name2,
max(case when rn = 2 then value end) value2,
...
from (
select t.*, row_number() over(order by id) rn
from mytable t
) t
You need a column that defines the ordering of the rows in the original dataset (and of the columns in the resultset): I assumed id.

You might be better off putting the values into a string or JSON column. For instance, you can aggregate the names and values into separate strings:
select list_agg(name, ',') within group (order by name) as names,
list_agg(value, ',') within group (order by name) as values
from t;
Or into a single string:
select list_agg(name || ':' || value, ',') within group (order by name) as name_values
from t;
Note: The maximum length of strings in Oracle for this purpose is 2000 characters. So this only works on a small amount of data.

Related

Conditional grouping of records

I have a problem with grouping records in PostgreSQL. I have a structure containing 3 columns, non unique id, name, group (it's old system and I can't change this structure).
Sample records:
id | name | group
-----+----------+------
1 | product1 | 0
1 | product1 | test
2 | product2 | test
3 | product3 | test123
I want the groups unequal 0 to be concatenated (get the id, name of the first record from the group).
The expected result:
id | name | group
-----+----------+------
1 | product1 | 0
1 | product1 | test
3 | product3 | test123
Currently count records in the following way:
SELECT
COUNT(CASE WHEN group = '0' THEN group END) +
COUNT(DISTINCT CASE WHEN group <> '0' THEN group END) AS count
FROM
table
Is it correct way? How can I convert it to retrieve records?
You can use row_number():
select id, name, group
from (select t.*, row_number() over (partition by group order by id) as seqnum
from t
) t
where seqnum = 1 or group = '0';
Note: group is a really bad name for a column. It is a SQL keyword, so you should escape the name. I am leaving it as is because your query uses it.

How to partition and find the most latest value in SQL

I have a table as follows:
ID | col1 | Date Time
1 | WA | 2/11/20
1 | CI | 1/11/20
2 | CI | 2/11/20
2 | WA | 3/11/20
3 | WA | 2/10/20
3 | WA | 1/11/20
3 | WA | 2/11/20
4 | WA | 1/10/20
4 | CI | 2/10/20
4 | SA | 3/10/20
I want to find all ID values for which col1 had some other value in addition to WA as well and the most latest value in col1 should be 'WA'. i.e. from the sample data above , only ID values 1 & 2 should be returned. Because both of those have an additional value (i.e., CI) in additon to WA, but still the most latest value for them is WA.
How do I get that??
FYI, there could be some IDs that don't have WA value at all. I want to eliminate them. Also those that only have WA value, I want to eliminate those as well.
Thanks for the help.
You can use window functions for this:
select distinct id
from (
select
t.*,
last_value(col1) over(partition by id oder by datetime) last_col1,
min(col1) over(partition by id) min_col1,
max(col1) over(partition by id) max_col1
from mytable t
) t
where last_col1 = 'WA' and min_col1 <> max_col1
The inner query uses last_value() to recover the last value of col1 for the given id, and computes the min and max values in the same partition.
Then, the outer query filters on ids whose last value is 'WA' and that have at least two distinct values (which is phrased as the inequality of the min and max value).
You can do this with aggregation:
select id
from t
group by id
having min(col1) <> max(col1) and -- at least two different values
max(case when col1 = 'WA' then datetime end) = max(datetime) -- last is WA

Get row which matched in each group

I am trying to make a sql query. I got some results from 2 tables below. Below results are good for me. Now I want those values which is present in each group. for example, A and B is present in each group(in each ID). so i want only A and B in result. and also i want make my query dynamic. Could anyone help?
| ID | Value |
|----|-------|
| 1 | A |
| 1 | B |
| 1 | C |
| 1 | D |
| 2 | A |
| 2 | B |
| 2 | C |
| 3 | A |
| 3 | B |
In the following query, I have placed your current query into a CTE for further use. We can try selecting those values for which every ID in your current result appears. This would imply that such values are associated with every ID.
WITH cte AS (
-- your current query
)
SELECT Value
FROM cte
GROUP BY Value
HAVING COUNT(DISTINCT ID) = (SELECT COUNT(DISTINCT ID) FROM cte);
Demo
The solution is simple - you can do this in two ways at least. Group by letters (Value), aggregate IDs with SUM or COUNT (distinct values in ID). Having that, choose those letters that have the value for SUM(ID) or COUNT(ID).
select Value from MyTable group by Value
having SUM(ID) = (SELECT SUM(DISTINCT ID) from MyTable)
select Value from MyTable group by Value
having COUNT(ID) = (SELECT COUNT(DISTINCT ID) from MyTable)
Use This
WITH CTE
AS
(
SELECT
Value,
Cnt = COUNT(DISTINCT ID)
FROM T1
GROUP BY Value
)
SELECT
Value
FROM CTE
WHERE Cnt = (SELECT COUNT(DISTINCT ID) FROM T1)

Get values on 1 line based on unique identifier

I'm trying to write a query to get the values of a table placed onto a single line based on a specific key.
table.ID | table.ACCOUNT |
==================================
12345 | 456789 |
12345 | ABCDEF |
12345 | HIJKLM |
For example, I want to get all the ACCOUNTs for ID 12345 (above) onto one line so it looks like what is below.
table.ID | table.ACCOUNT1 | table.ACCOUNT2 | table.ACCOUNT3 |
====================================================================
12345 | 456789 | ABCDEF | HIJKLM |
I think I want to join the table to itself but I keep getting the same values in the 2nd and 3rd ACCOUNT fields (i.e. 456789 shows up in all 3).
If you don't really need separate columns for all of the accounts, consider using an aggregate function such as string_agg in Postgres:
SELECT id, string_agg(account, ',') FROM table GROUP BY id
This will produce a result with two columns, the id and a string containing all of the accounts for the id separated by , characters.
If you know there are at most three accounts per id, you can pivot the data. In most databases, you can use row_number() and conditional aggregation:
select id,
max(case when seqnum = 1 then account end) as account_1,
max(case when seqnum = 2 then account end) as account_2,
max(case when seqnum = 3 then account end) as account_3
from (select t.*
row_number() over (partition by id order by id) as seqnum
from t
) t
group by id;
Unlike a string aggregation method, this puts the values into separate columns.

Select a different column from the row containing the max

My query looks like this:
SELECT [ScriptName]
,[BranchName]
,AVG([XMLColumn].value('count(//data)', 'int'))
,MIN([XMLColumn].value('count(//data)', 'int'))
,MAX([XMLColumn].value('count(//data)', 'int'))
FROM temp
GROUP BY [BranchName], [ScriptName]
The table I'm querying is structured like this:
ScriptName | BranchName | XMLLog | Developer | Revision
Currently, my results query produces output like this:
ScriptName | BranchName | Average | Min | MAX
-------------------------------------------------
Script 1 | trunk | 80 | 11 | 120
Script 2 | branch1 | 15 | 11 | 21
I want to add two columns to my results table: the developer from the row containing the minimum value and the developer from the row containing the maximum value. This would result in the output from the query looking like this:
ScriptName | BranchName | Average | Min | MAX | DeveloperWhoCausedMinimum | DeveloperWhoCausedMaximum
-----------------------------------------------------------------------------------------------------------------
Script 1 | trunk | 80 | 11 | 120 | me | The Boss
Script 2 | branch1 | 15 | 11 | 21 | me | The Boss
I am not sure where to start on this. Thanks!
The following uses window function to calculate the three values. It then selects the entire row with the max value:
select t.*, avgval, minval, maxval
from (select t.*,
avg([XMLColumn].value('count(//data)', 'int') over
(partition by BranchName, ScriptName) as avgval,
min([XMLColumn].value('count(//data)', 'int') over
(partition by BranchName, ScriptName) as minval,
max([XMLColumn].value('count(//data)', 'int') over
(partition by BranchName, ScriptName) as maxval
from temp t
) t
where [XMLColumn].value('count(//data)', 'int') = maxval;
Note: if multiple rows have the maximum value, you will get multiple rows out. If you want only one, then use row_number() instead.
EDIT:
Oh, you changed the question to be one column from two different rows rather than two rows from one column.
Use the same idea but with aggregation:
select BranchName, ScriptName, minval, avgval, maxval,
avg([XMLColumn].value('count(//data)', 'int') as avgval,
min([XMLColumn].value('count(//data)', 'int') as minval,
max([XMLColumn].value('count(//data)', 'int') as maxval,
max(case when seqnum = 1 then Developer end) as minDeveloper,
max(case when seqnum = cnt then Developer end) as maxDeveloper
from (select t.*,
row_number() over (partition by BranchName, ScriptName
order by [XMLColumn].value('count(//data)', 'int')
) as seqnum,
count(*) over (partition by BranchName, ScriptName) as cnt
from temp t
) t
group by BranchName, ScriptName;