Get values on 1 line based on unique identifier - sql

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.

Related

Select all values (all rows) in one row Oracle

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.

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

Reverse col and rows in SQL

I have to create query, to reverse rows and cols correct. I am using MS SQL SERVER 2016.
This is what I have:
Row_ID | Group_ID | Group_Status | MemberRole | name
2807 | 10568 | accept | chairman | Rajah
2808 | 10568 | accept | member | Vaughan
2812 | 10568 | accept | secretary | Susan
This is what I need:
Group_ID | Status | Chairman | Secretary | Member1 | Member2 | Member3 | ... | Member20
10568 | Accept | Rajah | Susan | Vaughan | Kane | Oprah | ... | Imelda
(users with member role can be between 0-20)
Probably I should use pivot, but I have no idea how.
Ok, I have this code:
SELECT *
FROM
(
SELECT group_id,
group_status,
memberRole,
name
FROM DataGroup
) dataSource PIVOT(MAX(name) FOR memberRole IN([chairman],
[secretary],
[member])) pivotTab;
But I losing rows with members (get only one member), how to extract them to columns?
You can try this with a unioned query:
Some mockup (please provide such a dummy table with your sample data yourself in your next question):
DECLARE #mockup TABLE(Row_ID INT,Group_ID INT,Group_Status VARCHAR(100),MemberRole VARCHAR(100),[name] VARCHAR(100));
INSERT INTO #mockup VALUES
(2807,10568,'accept','chairman','Rajah')
,(2808,10568,'accept','member','Vaughan')
,(2812,10568,'accept','secretary','Susan')
,(2899,10568,'accept','member','Onemore');
--The query
SELECT p.*
FROM
(
SELECT Group_ID
,Group_Status
,[name]
,MemberRole
FROM #mockup
WHERE MemberRole IN('chairman','secretary')
UNION ALL
SELECT Group_ID
,Group_Status
,[name]
,CONCAT('Member',ROW_NUMBER() OVER(PARTITION BY Group_ID ORDER BY Row_ID))
FROM #mockup
WHERE MemberRole='member'
) t
PIVOT
(
MAX([name]) FOR MemberRole IN(Chairman,Secretary,Member1,Member2,Member3 /*add as many as you need*/)
) p;
The result
Group_ID Group_Status Chairman Secretary Member1 Member2 Member3
10568 accept Rajah Susan Vaughan Onemore NULL
In short:
The first part of the query will Just pick the two fix names.
The second part will pick the members and number them sorted by their Row_ID.
The PIVOT will then transform this to a single row, using the column MemberRole for the new column names.
You will have to think about some more things:
What if not all the lines are accepted?
What of there are many groups?
If you need help, you can comeback with a new question. Happy Coding!
I would simply use conditional aggregation:
select group_id, group_status,
max(case when member_role = 'chairman' then name end) as chairman,
max(case when member_role = 'secretary' then name end) as secretary,
max(case when member_role = 'member' and seqnum = 1 then name end) as member_01,
max(case when member_role = 'member' and seqnum = 2 then name end) as member_02,
. . .
from (select m.*,
row_number() over (partition by group_id, member_role order by row_id) as seqnum
from #mockup m
) m
group by group_id, group_status;
I find conditional aggregation to be much more flexible than pivot. This is an example of the situation where the query is simpler.

SQL Query find users with only one product type

I solemnly swear I did my best to find an existing question, may I'm not sure how to phrase it correctly.
I would like to return records for users that have quota for only one product type.
| user_id | product |
| 1 | A |
| 1 | B |
| 1 | C |
| 2 | B |
| 3 | B |
| 3 | C |
| 3 | D |
In the example above I'd like a query that only returns users who carry quota for only one product type - doesn't really matter which product at this point.
I tried using select user_id, product from table group by 1,2 having count(user) < 2 but this does not work, nor does select user_id, product from table group by 1,2 having count(*) < 2
Any help is appreciated.
Your having clause is good; the issue's with your group by. Try this:
select user_id
, count(distinct product) NumberOfProducts
from table
group by user_id
having count(distinct product) = 1
Or you could do this; which is closer to your original:
select user_id
from table
group by user_id
having count(*) < 2
The group by clause can't take ordinal arguments (like, e.g., the order by clause can). When grouping by a value like 1, you're in fact grouping by the literal value 1, which would just be the same for any row in the table, and thus will group all the rows in the table to one group. Since there are more than one product in the entire table, no rows will be returned.
Instead, you should group by the user_id:
SELECT user_id
FROM mytable
GROUP BY user_id
HAVING COUNT(*) = 1
If you want the product, then do:
select user_id, max(product) as product
from table
group by user_id
having min(product) = max(product);
The having clause could also be:
having count(distinct product) = 1