Postgresql : Mark the first row of a group - sql

I have a table t like this :
id | group_id | name
------------------------
1 | 1 | richard
2 | 1 | ray
3 | 2 | enzo
4 | 2 | shiela
5 | 2 | anne
I have no problem selecting each group, however I want to mark the first occurrence for each group by group_id. Then add it as column to mark that the row is the first occurrence of that group.
E.g, Richard for group 1, or Enzo for group 2 and so on.
I should be able to use:
select
t.*
case
when (condition)
...(boolean result here)
end as is_first_row
from t
and result to :
id | group_id | name |is_first_row
-------------------------------
1 | 1 | richard | t
2 | 1 | ray | f
3 | 2 | enzo | t
4 | 2 | shiela | f
5 | 2 | anne | f
How do I formulate the condition statement for the select query?

Use row_number():
with my_table(id, group_id, name) as (
values
(1, 1, 'richard'),
(2, 1, 'ray'),
(3, 2, 'enzo'),
(4, 2, 'shiela'),
(5, 2, 'anne')
)
select *, row_number() over w = 1 as is_first_row
from my_table
window w as (partition by group_id order by id);
id | group_id | name | is_first_row
----+----------+---------+--------------
1 | 1 | richard | t
2 | 1 | ray | f
3 | 2 | enzo | t
4 | 2 | shiela | f
5 | 2 | anne | f
(5 rows)
Select row_number() to see how it works. Row numbers are calculated in partitions by group_id i.e. for every group_id separately, in order by id:
with my_table(id, group_id, name) as (
values
(1, 1, 'richard'),
(2, 1, 'ray'),
(3, 2, 'enzo'),
(4, 2, 'shiela'),
(5, 2, 'anne')
)
select *, row_number() over w
from my_table
window w as (partition by group_id order by id);
id | group_id | name | row_number
----+----------+---------+------------
1 | 1 | richard | 1
2 | 1 | ray | 2
3 | 2 | enzo | 1
4 | 2 | shiela | 2
5 | 2 | anne | 3
(5 rows)

please check my answer and let me know in case of any error in the logic
Create Table #Temp(id int,group_id int,name nvarchar(max))
Insert into #Temp values
(1,1,'richard')
,(2,1,'ray')
,(3,2,'enzo')
,(4,2,'shiela')
,(5,2,'anne')
Select t2.id,t2.group_id,t2.name,t1.group_id_c, case
when t1.group_id_c=1 then 't'
else 'f'
end AS is_firstrow from #temp t2 join
(Select t.*, row_number() over (partition by group_id order by id) as group_id_c from #Temp t ) t1
on t1.id=t2.id

Related

Calculate how many rows are ahead of position in column when condition is met

How can I calculate how many people are ahead of Jane on Floor 2 (not including those on floor 1)?
+------+---------+----------+
|Index | Name | Floor |
+------+---------+----------+
| 1 | Sally | 1 |
| 2 | Sue | 1 |
| 3 | Fred | 1 |
| 4 | Wally | 2 |
| 5 | Tommy | 2 |
| 6 | Jane | 2 |
| 7 | Bart | 2 |
| 8 | Sam | 3 |
+------+---------+----------+
The expected result is 2 as there are 2 people (Wally & Tommy) ahead of Jane on floor 2.
I've tried using CHARINDEX to find the row number from a temp table that I've generated but that doesn't seem to work:
SELECT CHARINDEX('Jane', Name) as position
INTO #test
FROM tblExample
WHERE Floor = 2
select ROW_NUMBER() over (order by position) from #test
WHERE position = 1
I think a simple row_number() would do the trick
Select Value = RN-1
From (
Select *
,RN = row_number() over (partition by [floor] order by [index])
From YourTable
Where [Floor]=2
) A
Where [Name]='Jane'
You could do:
select count(*)
from t
where t.floor = 2 and
t.id < (select t2.id from t t2 where t2.name = 'Jane' and t2.floor = 2);
With an index on (floor, name, id), I would expect this to be faster than row_number().

Oracle Connect_is_leaf similar in SQL server

Here is my query which is in Oracle PL/SQL syntax, How can I Change it to SQL server format?
Any alternatives for Connect_by_isleaf?
(
select PARTY_KEY, ltrim(sys_connect_by_path(alt_name, '|'), '|') AS alt_name_list
from
(select PARTY_KEY, alt_name, row_number() over(partition by PARTY_KEY order by alt_name) rno
from (
select party_key, (select alt_name_type_desc from "CRMS"."PRJ_APP_ALT_NAME_TYPE" where alt_name_type_cd = alt_name_type) || ' - ' || alt_name as alt_name
from "CDD_PROFILES"."PRJ_PRF_ALT_NAME" order by party_key, alt_name_type
) alt
)
where connect_by_isleaf = 1
connect by PARTY_KEY = prior PARTY_KEY
and rno = prior rno+1
start with rno = 1
)
tried to use With AS clause but it is not working somehow.
Thanks in advance
The equivalent in SQL Server is called a "recursive CTE".
You can read about it here:
https://learn.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-2017
Oracle Hierarchical queries can be rewritten as recursive CTE statements in databases that support them (SQL Server included). A classic set of hierarchical data would be an organization hierarchy such as the one below:
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE ORGANIZATIONS
([ID] int primary key
, [ORG_NAME] varchar(30)
, [ORG_TYPE] varchar(30)
, [PARENT_ID] int foreign key references organizations)
;
INSERT INTO ORGANIZATIONS
([ID], [ORG_NAME], [ORG_TYPE], [PARENT_ID])
VALUES
(1, 'ACME Corp', 'Company', NULL),
(2, 'Finance', 'Division', 1),
(6, 'Accounts Payable', 'Department', 2),
(7, 'Accounts Receivables', 'Department', 2),
(8, 'Payroll', 'Department', 2),
(3, 'Operations', 'Division', 1),
(4, 'Human Resources', 'Division', 1),
(10, 'Benefits Admin', 'Department', 4),
(5, 'Marketing', 'Division', 1),
(9, 'Sales', 'Department', 5)
;
In the recursive t1 below the select statement before the union all is the anchor query and the select statement after the union all is the recursive part. The recursive part has exactly one reference to t1 in its from clause. The org_path column simulates oracles sys_connect_by_path function concatenating the org_names together. The level column simulates oracles LEVEL pseudo column and is utilized in the output query to determine the leaf status (is_leaf column) similar to oracles connect_by_isleaf pseudo column:
with t1(id, org_name, org_type, parent_id, org_path, level) as (
select o.*
, cast('|' + org_name as varchar(max))
, 1
from organizations o
where parent_id is null
union all
select o.*
, t1.org_path+cast('|'+o.org_name as varchar(max))
, t1.level+1
from organizations o
join t1
on t1.id = o.parent_id
)
select t1.*
, case when t1.level < lead(t1.level) over (order by org_path) then 0 else 1 end is_leaf
from t1 order by org_path
Results:
| id | org_name | org_type | parent_id | org_path | level | is_leaf |
|----|----------------------|------------|-----------|-------------------------------------------|-------|---------|
| 1 | ACME Corp | Company | (null) | |ACME Corp | 1 | 0 |
| 2 | Finance | Division | 1 | |ACME Corp|Finance | 2 | 0 |
| 6 | Accounts Payable | Department | 2 | |ACME Corp|Finance|Accounts Payable | 3 | 1 |
| 7 | Accounts Receivables | Department | 2 | |ACME Corp|Finance|Accounts Receivables | 3 | 1 |
| 8 | Payroll | Department | 2 | |ACME Corp|Finance|Payroll | 3 | 1 |
| 4 | Human Resources | Division | 1 | |ACME Corp|Human Resources | 2 | 0 |
| 10 | Benefits Admin | Department | 4 | |ACME Corp|Human Resources|Benefits Admin | 3 | 1 |
| 5 | Marketing | Division | 1 | |ACME Corp|Marketing | 2 | 0 |
| 9 | Sales | Department | 5 | |ACME Corp|Marketing|Sales | 3 | 1 |
| 3 | Operations | Division | 1 | |ACME Corp|Operations | 2 | 1 |
To select just the leaf nodes, change the output query from above to another CTE (T2) dropping the order by clause or moving it to final output query and limiting by the is_leaf column:
with t1(id, org_name, org_type, parent_id, org_path, level) as (
select o.*
, cast('|' + org_name as varchar(max))
, 1
from organizations o
where parent_id is null
union all
select o.*
, t1.org_path+cast('|'+o.org_name as varchar(max))
, t1.level+1
from organizations o
join t1
on t1.id = o.parent_id
), t2 as (
select t1.*
, case when t1.level < lead(t1.level) over (order by org_path) then 0 else 1 end is_leaf
from t1
)
select * from t2 where is_leaf = 1
Results:
| id | org_name | org_type | parent_id | org_path | level | is_leaf |
|----|----------------------|------------|-----------|-------------------------------------------|-------|---------|
| 6 | Accounts Payable | Department | 2 | |ACME Corp|Finance|Accounts Payable | 3 | 1 |
| 7 | Accounts Receivables | Department | 2 | |ACME Corp|Finance|Accounts Receivables | 3 | 1 |
| 8 | Payroll | Department | 2 | |ACME Corp|Finance|Payroll | 3 | 1 |
| 10 | Benefits Admin | Department | 4 | |ACME Corp|Human Resources|Benefits Admin | 3 | 1 |
| 9 | Sales | Department | 5 | |ACME Corp|Marketing|Sales | 3 | 1 |
| 3 | Operations | Division | 1 | |ACME Corp|Operations | 2 | 1 |
Alternatively if you realize that leaf nodes can be identified by their lack of child nodes, you can flip this on its head and start with the leaf nodes, and search up the tree, retaining all the original record values, building out the org_path in reverse, and passing along the next parent id as next_id. In the final output, stage, selecting only those records whose next_id is null will yield the same results as the prior query:
with t1(id, org_name, org_type, parent_id, org_path, level, next_id) as (
select o.*
, cast('|'+org_name as varchar(max))
, 1
, parent_id
from organizations o
where not exists (select 1 from organizations c where c.parent_id = o.id)
union all
select t1.id
, t1.org_name
, t1.org_type
, t1.parent_id
, cast('|'+p.org_name as varchar(max))+t1.org_path
, level+1
, p.parent_id
from organizations p
join t1
on t1.next_id = p.id
)
select * from t1 where next_id is null order by org_path
Results:
| id | org_name | org_type | parent_id | org_path | level | next_id |
|----|----------------------|------------|-----------|-------------------------------------------|-------|---------|
| 6 | Accounts Payable | Department | 2 | |ACME Corp|Finance|Accounts Payable | 3 | (null) |
| 7 | Accounts Receivables | Department | 2 | |ACME Corp|Finance|Accounts Receivables | 3 | (null) |
| 8 | Payroll | Department | 2 | |ACME Corp|Finance|Payroll | 3 | (null) |
| 10 | Benefits Admin | Department | 4 | |ACME Corp|Human Resources|Benefits Admin | 3 | (null) |
| 9 | Sales | Department | 5 | |ACME Corp|Marketing|Sales | 3 | (null) |
| 3 | Operations | Division | 1 | |ACME Corp|Operations | 2 | (null) |
One of these two methods may prove more performant than the other, but you'll need to try them each out on your data to see which one works better.

Using Limit on Distinct group by values psql

Suppose I have a table that looks like this or maybe I am going nowhere.
create table customers (id text, name text, number int, useless text);
With values
insert into customers (id, name, number, useless)
values
('1','apple',1, 'a'),
('2','banana',3, 'b'),
('3','pear',2, 's'),
('4','apple',1,'e'),
('5','banana',3,'s'),
('6','cherry',3, 'a'),
('7','cherry',4, 's'),
('8','apple',2, 'd'),
('9','banana',4, 'c'),
('10','pear',5, 'e');
My failed psql query is this.
select id, name, number, useless
from customers
where number < 4
group by customers.name limit 2
the query i want to use that it returns first 2 unique grouped by customers.name. Not the first 2 rows
In the end I want it to return
('1','apple',1, 'a'),
('4','apple',1,'e'),
('8','apple',2, 'd'),
('2','banana',3, 'b'),
('5','banana',3,'s'),
so it returns the first 2 grouped names.
How can I make this query?
Thank you.
Edit:
this query is my second try I know I am kinda close.
select t.id, t.name, t.ranking
from (
SELECT id, name, dense_rank() OVER (order by name) as
ranking
FROM customers
group by name
) t
where t.ranking < 3
try this:
select id, name, number, useless
from customers
where name in (
select name
from customers
where number < 4
group by customers.name
order by name limit 2
)
| id | name | number | useless |
|----|--------|--------|---------|
| 1 | apple | 1 | a |
| 2 | banana | 3 | b |
| 4 | apple | 1 | e |
| 5 | banana | 3 | s |
| 8 | apple | 2 | d |
| 9 | banana | 4 | c |
SQL Fiddle DEMO
The group by customers.name function do not order your output, just group them by the customers.name, what you want to do is to order the group right? So what i think you want to do is:
select id, name, number, useless
from customers
group by name
order by name []*
*[asc/desc] depends of what order you want to do:
asc - ascendent,
desc - descendent
Hope it helps you.
You can use dense_rank() as:
SELECT * FROM (
SELECT DENSE_RANK() OVER (order by name) AS rank, temp.*
FROM customers temp WHERE number < 4) data
WHERE data.rank <= 2
| rank| id| name | number | useless |
|-----|---|--------|--------|---------|
| 1 | 4 | apple | 1 | e |
| 1 | 1 | apple | 1 | a |
| 1 | 8 | apple | 2 | d |
| 2 | 5 | banana | 3 | s |
| 2 | 2 | banana | 3 | b |

how to convert a table into another based on each Columns in SQL

I have the following table(each name is unique):
TABLE1:
+----+----------+----------------+----------------+----------------+----------------+
| id | workflow | tire1_approver | tire2_approver | tire3_approver | tire4_approver |
+----+----------+----------------+----------------+----------------+----------------+
| 1 | 1 | John | Mike | Tom | Kevin |
+----+----------+----------------+----------------+----------------+----------------+
| 2 | 2 | Mike | Andrew | An | Bob |
+----+----------+----------------+----------------+----------------+----------------+
I need to translate it into the following table, a person can appear more than once:
TABLE2:
+----+--------+----------------+-----------+----------------------+
| ID | Name | Position | Workflow | upper_level_approver |
+----+--------+----------------+-----------+----------------------+
| 1 | John | tire1_approver | 1 | Mike |
+----+--------+----------------+-----------+----------------------+
| 2 | Mike | tire2_approver | 1 | Tom |
+----+--------+----------------+-----------+----------------------+
| 3 | Tom | tire3_approver | 1 | Kevin |
+----+--------+----------------+-----------+----------------------+
| 4 | Kevin | tire4_approver | 1 | N/A |
+----+--------+----------------+-----------+----------------------+
| 5 | Mike | tire1_approver | 2 | Andrew |
+----+--------+----------------+-----------+----------------------+
| 6 | Andrew | tire2_approver | 2 | An |
+----+--------+----------------+-----------+----------------------+
| 7 | An | tire3_approver | 2 | Bob |
+----+--------+----------------+-----------+----------------------+
| 8 | Bob | tire4_approver | 2 | N/A |
+----+--------+----------------+-----------+----------------------+
I'm using sql developer, i have tried loop and join but aren't able to get to what I want.
TABLE1 and TABLE2 are both in the database,
Ultimately I would like to store this in the a stored Procedure, when the Front end makes an update to TABLE1, it also calls this procedure and automatically updates TABLE2.
please help
Here is a solution using a cross join (instead of the unpivot operator). This will work in older versions of Oracle. Simulated data and query output are the same as in my other answer (with unpivot).
The OP did not mention whether null is possible in the input table (in the approver columns). If it is possible, this cross join solution will handle them differently from the unpivot solution. The unpivot solution can be modified to produce the same result as the cross join, by using the optional include nulls directive in the unpivot clause. Or, if the null values should not be included, that can be handled in the cross join solution with a where condition.
select id, name, position, workflow,
lead(name, 1, 'N/A') over (partition by id, workflow order by lvl)
as upper_level_approver
from ( select t.id,
case h.lvl when 1 then t.tire1_approver
when 2 then t.tire2_approver
when 3 then t.tire3_approver
when 4 then t.tire4_approver
end
as name,
case h.lvl when 1 then 'tire1_approver'
when 2 then 'tire2_approver'
when 3 then 'tire3_approver'
when 4 then 'tire4_approver'
end
as position,
t.workflow,
h.lvl
from table1 t
cross join
( select level as lvl from dual connect by level <= 4 ) h
)
;
Depending on your Oracle version, you may or may not be able to use the solution below. It uses the UNPIVOT operator, so it requires Oracle 11.2 or higher. In earlier versions, you can use a cross join.
with
table1 ( id, workflow, tire1_approver, tire2_approver,
tire3_approver, tire4_approver )
as (
select 1, 1, 'John', 'Mike' , 'Tom', 'Kevin' from dual union all
select 2, 2, 'Mike', 'Andrew', 'An' , 'Bob' from dual
)
-- End of simulated table (for testing; not part of the solution).
-- SQL query begins BELOW THIS LINE
select id, name, position, workflow,
lead(name, 1, 'N/A') over ( partition by id, workflow order by lvl )
as upper_level_approver
from table1
unpivot ( name for (position, lvl) in ( tire1_approver as ('tire1_approver', 1),
tire2_approver as ('tire2_approver', 2),
tire3_approver as ('tire3_approver', 3),
tire4_approver as ('tire4_approver', 4)
)
)
;
ID NAME POSITION WORKFLOW UPPER_LEVEL_APPROVER
-- ------ -------------- --------- --------------------
1 John tire1_approver 1 Mike
1 Mike tire2_approver 1 Tom
1 Tom tire3_approver 1 Kevin
1 Kevin tire4_approver 1 N/A
2 Mike tire1_approver 2 Andrew
2 Andrew tire2_approver 2 An
2 An tire3_approver 2 Bob
2 Bob tire4_approver 2 N/A

Postgresql - Return (N) rows for each ID

I have a table like this
contact_id | phone_number
1 | 55551002
1 | 55551003
1 | 55551000
2 | 55552001
2 | 55552008
2 | 55552003
2 | 55552007
3 | 55553001
3 | 55553002
3 | 55553009
3 | 55553004
4 | 55554000
I want to return only 3 numbers of each contact_id, order by phone_number, like this:
contact_id | phone_number
1 | 55551000
1 | 55551002
1 | 55551003
2 | 55552001
2 | 55552003
2 | 55552007
3 | 55553001
3 | 55553002
3 | 55553004
4 | 55554000
please need be an optimized query.
My Query
SELECT a.cod_cliente, count(a.telefone) as qtd
FROM crm.contatos a
LEFT JOIN (
SELECT *
FROM crm.contatos b
LIMIT 3
) AS sub_contatos ON sub_contatos.cod_contato = a.cod_cliente
group by a.cod_cliente;
This type of query can easily be solved using window functions:
select contact_id, phone_number
from (
select contact_id, phone_number,
row_Number() over (partition by contact_id order by phone_number) as rn
from crm.contatos
) t
where rn <= 3
order by contact_id, phone_number;