Conditional grouping of records - sql

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.

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.

How to include row totals in pivot statement in Oracle?

I have a table of data, see
Using a pivot statement, I am able to break down the count by title
select * from (
select * from ta
)
pivot (
COUNT(title)
for title in ( 'worker', 'manager') )
So the result looks like this:
STATUS 'worker' 'manager'
started 3 1
finished 4 5
ready 3 4
What I need to add a third column for the row totals
STATUS 'worker' 'manager' Total
started 3 1 4
finished 4 5 9
ready 3 4 7
Any idea how I can accomplish this within the same statement?
demo is at http://sqlfiddle.com/#!4/740fd/1
I would just use conditional aggregation rather than pivot. This gives you the extra flexibility that you need:
select
status,
sum(case when title = 'worker' then 1 else 0 end) worker,
sum(case when title = 'manager' then 1 else 0 end) manager,
count(*) total
from ta
group by status
Demo on DB Fiddle:
STATUS | WORKER | MANAGER | TOTAL
:------- | -----: | ------: | ----:
started | 3 | 1 | 4
finished | 4 | 5 | 9
ready | 3 | 4 | 7
Use the SUM() analytic function to get the total and then use PIVOT
select
status,
sum(case
when title = 'worker'
then 1
else 0
end) worker,
sum(case
when title = 'manager'
then 1
else 0
end) manager,
count(*) total
from ta
group by status
Give an alias for the whole query(such as q) in order to qualify the all columns with asterisk(q.*), and then sum up all the columns to yield total column next to it :
select q.*, worker + manager as total
from ta
pivot
(
count(title)
for title in ( 'worker' as worker, 'manager' as manager )
) q
Demo
I think the other examples are much simpler, but here is a different approach using cube and grouping before pivoting:
select *
from (
select decode(grouping(title),1,'total',0,title) title,
status,
count(*) cnt
from ta
group by status, cube(title) )
pivot(
sum(cnt) for title in ('worker','manager','total')
)
Output:
| STATUS | 'worker' | 'manager' | 'total' |
|----------|----------|-----------|---------|
| finished | 4 | 5 | 9 |
| ready | 3 | 4 | 7 |
| started | 3 | 1 | 4 |
http://sqlfiddle.com/#!4/740fd/13/0
Adding the cube into the group by clause will give you a subtotal for that column. It will show as null in that column by default. You can use the grouping function in the select clause to differentiate between the total row and the normal rows (the total row will be 1, normal rows are 0). Using a decode will force those total rows to be 'total' which becomes one of the values that you can pivot on.

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

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)

Query to get multiple row values into multiple columns

I have a CSV file with 2 columns .
Empid | SID
:-----|-----:
12312 | S-1-5-21-3751615294
12312 | S-1-5-21-3751615298
12312 | S-1-5-21-3751615292
12313 | S-1-5-21-3751615294-5078
13546 | S-1-5-21-3751615294-50725
12312 | S-1-5-21-3751615291
14151 | S-1-5-21-3751615294-50722
For an Empid there are multiple SIDs available .I need help writing a sql SELECT query that can map(and store) these SIDs(sorted) into multiple columns.
Desired SQL Select output is below :-
+--------+---------------------------+---------------------+--------------------+--------------------+
| Empid | SID1 | SID2 | SID3 | SID4 |
+--------+---------------------------+---------------------+--------------------+--------------------+
| 12312 | S-1-5-21-3751-65291 | S-1-5-21-375165292 | S-1-5-21-375165294 | S-1-5-21-375165298 |
| 12313 | S-1-5-21-3751615294-5078 | NULL | NULL | NULL |
| 13546 | S-1-5-21-3751615294-50725 | NULL | NULL | NULL |
+--------+---------------------------+---------------------+--------------------+--------------------+
I am collecting an employee record in my application collector(using sql select queries) from a CSV file and need to collect these SIDs in his record .Maximum 4 SIDs can be possible so I created 4 attributes for SIDs .
Thanks in advance .
Presumably, you know the number of columns. If so, you can do it using conditional aggregation and row_number():
select empid,
max(case when seqnum = 1 then sid end) as sid_1,
max(case when seqnum = 2 then sid end) as sid_2,
max(case when seqnum = 3 then sid end) as sid_3,
max(case when seqnum = 4 then sid end) as sid_4
from (select t.*, row_number() over (partition by empid order by empid) as seqnum
from t
) t
group by empid;
If you don't know the number, then perhaps a comma-delimited list will do:
select empid, listagg(sid, ',') within group (order by sid) as sids
from t
group by empid;
A SQL query has a fixed number of columns, so a result set that has a flexible number of columns would require dynamic SQL.
You can use window function row_number() to assign row number to sid within each empid and then use conditional aggregation to get the final results.
select
empid,
min(case when rn = 1 then sid end) sid1,
min(case when rn = 2 then sid end) sid2,
min(case when rn = 3 then sid end) sid3,
min(case when rn = 4 then sid end) sid4
from (select
t.*,
row_number() over (partition by empid order by sid) rn
from table t) group by empid;