Query to get multiple row values into multiple columns - sql

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;

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 can i check the order of column values(by date) for every unique id?

I have this table, Activity:
| ID | Date of activity | activity |
|----|---------------------|----------|
| 1 | 2016-05-01T13:45:03 | a |
| 1 | 2016-05-02T13:45:03 | b |
| 1 | 2016-05-03T13:45:03 | a |
| 1 | 2016-05-04T13:45:03 | b |
| 2 | 2016-05-01T13:45:03 | b |
| 2 | 2016-05-02T13:45:03 | b |
and this table:
| id | Right order |
|----|-------------|
| 1 | yes |
| 2 | no |
How can I check for every ID if the order of the activities is sumiliar to this order for example ?
a b a b a b ..
of course i'll check according to activity date
In SQL Server 2012+ you could use common table expression with lag(), and then the min() of a case expression that follows your logic like so:
;with cte as (
select *
, prev_activity = lag(activity) over (partition by id order by date_of_activity)
from t
)
select id
, right_order = min(case
when activity = 'a' and isnull(prev_activity,'b')<>'b' then 'no'
when activity = 'b' and isnull(prev_activity,'b')<>'a' then 'no'
else 'yes'
end)
from cte
group by id
rextester demo: http://rextester.com/NQQF78056
returns:
+----+-------------+
| id | right_order |
+----+-------------+
| 1 | yes |
| 2 | no |
+----+-------------+
Prior to SQL Server 2012 you can use outer apply() to get the previous activity instead of lag() like so:
select id
, right_order = min(case
when activity = 'a' and isnull(prev_activity,'b')<>'b' then 'no'
when activity = 'b' and isnull(prev_activity,'b')<>'a' then 'no'
else 'yes'
end)
from t
outer apply (
select top 1 prev_activity = i.activity
from t as i
where i.id = t.id
and i.date_of_activity < t.date_of_activity
order by i.date_of_activity desc
) x
group by id
EDITED - Allows for variable number of Patterns per ID
Perhaps another approach
Example
Declare #Pat varchar(max)='a b'
Declare #Cnt int = 2
Select ID
,RightOrder = case when rtrim(replicate(#Pat+' ',Hits/#Cnt)) = (Select Stuff((Select ' ' +activity From t Where id=A.id order by date_of_activity For XML Path ('')),1,1,'') ) then 'Yes' else 'No' end
From (Select ID,hits=count(*) from t group by id) A
Returns
ID RightOrder
1 Yes
2 No
select id,
case when sum(flag)=0 and cnt_per_id%2=0
and max(case when rnum=1 then activity end) = 'a'
and max(case when rnum=2 then activity end) = 'b'
and min_activity = 'a' and max_activity = 'b'
then 'yes' else 'no' end as RightOrder
from (select t.*
,row_number() over(partition by id order by activitydate) as rnum
,count(*) over(partition by id) as cnt_per_id
,min(activity) over(partition by id) as min_activity
,max(activity) over(partition by id) as max_activity
,case when lag(activity) over(partition by id order by activitydate)=activity then 1 else 0 end as flag
from tbl t
) t
group by id,cnt_per_id,max_activity,min_activity
Based on the explanation the following logic has to be implemented for rightorder.
Check if the number of rows per id are even (Remove this condition if there can be an odd number of rows like a,b,a or a,b,a,b,a and so on)
First row contains a and second b, min activity is a and max activity is b for an id.
Sum of flags (set using lag) should be 0

Can row_number() ignore null in oracle

I have data like this
---------------------------
| code | other column
---------------------------
| C | a
| null | a
| A | a
| null | a
| null | a
----------------------------
How can i write query to get row_number without counting null column.
----------------------------------
| id | code | other column |
----------------------------------
| 1 | C | a
| | null | a
| 2 | A | a
| | null | a
| | null | a
----------------------------------
Well, not specifically. But you can get what you want by using conditional logic:
select (case when code is not null
then row_number() over (partition by (case when code is not null then 1 else 0 end)
order by . . .
)
end) as id
It is not clear to me what the order by is for the row_number() which is what the . . . means.
If you need to order on code (descendent in your example) with NULLs last:
select
decode(code,null,null,row_number() over (order by code DESC NULLS LAST)) rn,
code
from test;
If you need to order on OTHER column:
select
decode(code,null,null,row_number() over (order by decode(code,null,null,'x') NULLS LAST, other DESC)) rn,
code, other
from test;
You can use row_number on the desired subset and then union all the other records:
select row_number() over (order by sortkey) as id, code, other_column
from mytable
where code is not null
union all
select null as id, code, other_column
from mytable
where code is null
order by sortkey;
Another easy way would be:
Select
CASE WHEN code IS NOT NULL THEN ROW_NUMBER() OVER (PARTITION BY code order by code)
ELSE NULL END id,
code,
other_column
FROM table;
Example, in my case using IS NOT NULL did not work for me, I had to change it to an expression:
SELECT A.ITEMNAME,
CASE
WHEN (SELECT T1.MAXSTOCK
FROM DATOS T1
WHERE T1.MAXSTOCK) > 5 THEN
ROW_NUMBER() OVER(PARTITION BY CASE
WHEN (SELECT T1.MAXSTOCK
FROM DATOS T1
WHERE T1.MAXSTOCK) <= 5 /*here should go IS NOT NULL*/
THEN
1
END ORDER BY A.ITEMNAME)
ELSE
NULL
END AS #ROW
FROM TABLE A

Pivot/Transpose telephone numbers stored in rows

I have seen a lot of posts and topics regarding pivots and transposing data, I'm either not understanding them (likely) or I'm trying to over complicate what I need to do.
I'm running a simple select statement like so:
SELECT Customer, Telephone
FROM DetailsTable
WHERE Customer = 74270571
GROUP BY Customer, Telephone
This returns:
Customer | Telephone
74270571 | 01556589962
74270571 | 07756563729
And what I am trying to get is
Customer | Tel1 | Tel2
74270571 | 01556589962 | 07756563729
There can be a max of 5 tel numbers.
Since you have multiple Telephone values for each Customer, the easiest way to get the result would be to use a windowing function like row_number to create a unique value for each Telephone/Customer combination. Once you have this value, then you can PIVOT the result using an aggregate function and CASE expression or the PIVOT function.
Having up to five telephone numbers, makes this easy to write the query as a hard-coded or static version. Using an aggregate function with a CASE expression the code would be:
select
customer,
Tel1 = max(case when rn = 1 then telephone else null end),
Tel2 = max(case when rn = 2 then telephone else null end),
Tel3 = max(case when rn = 3 then telephone else null end),
Tel4 = max(case when rn = 4 then telephone else null end),
Tel5 = max(case when rn = 5 then telephone else null end)
from
(
select customer, telephone,
rn = row_number() over(partition by customer order by telephone)
from DetailsTable
) x
group by customer;
See SQL Fiddle with Demo
If you want to use the PIVOT function, then the code would be:
select customer,
Tel1,
Tel2,
Tel3,
Tel4,
Tel4
from
(
select customer, telephone,
col = 'Tel'+cast(row_number() over(partition by customer order by telephone)
as varchar(1))
from DetailsTable
) x
pivot
(
max(telephone)
for col in (Tel1, Tel2, Tel3, Tel4, Tel5)
) p
See SQL Fiddle with Demo. Both give a result:
| CUSTOMER | TEL1 | TEL2 | TEL3 | TEL4 |
|----------|------------|------------|--------|--------|
| 74270571 | 1556589962 | 7756563729 | (null) | (null) |