I have a Postgres table which holds information for users, the fields in this table are:
id (int)
name (string)
I would like to be able to add a user, and providing him an ID that will be deferred from the current values that are in the table:
The ID that will be given to the added user should be the minimal that is available in the table i.e if I have a table with the following IDs: 1, 3, 4, 5, 8,
I would like the ID of the user to be 2, next time I'll add a user its ID will be 6, and the next one will be 7, and so on.
What would be the right query?
Following select query can be used to find out missed ids from table
SELECT t
FROM generate_series((
SELECT min(id) + 1
FROM tb
), (
SELECT max(id) + 1
FROM tb
)) t
WHERE NOT t IN (
SELECT id
FROM tb
)
ORDER BY t ASC
and the insert can be done by following ways,
INSERT INTO tb
VALUES (
(
SELECT t
FROM generate_series((
SELECT min(id) + 1
FROM tb
), (
SELECT max(id) + 1
FROM tb
)) t
WHERE NOT t IN (
SELECT id
FROM tb
)
ORDER BY t ASC limit 1
)
,'B'
)
OR
Create a function like this
CREATE OR replace FUNCTION missed_id ()
RETURNS INTEGER AS $$
SELECT t
FROM generate_series((
SELECT min(id) + 1
FROM tb
), (
SELECT max(id) + 1
FROM tb
)) t
WHERE NOT t IN (
SELECT id
FROM tb
)
ORDER BY t ASC limit 1;;$$
LANGUAGE sql
and insert should be
insert into tb values (missed_id(),'B')
insert into auth_user(id, user_name)
values((select a from generate_series((select min(id) from auth_user)
,(select max(id)+1 from auth_user)
) as a
left join auth_user on (a = id )
where id is null order by a limit 1)
, 'new user')
this is a bad idea, and its not transaction safe if you have simultaneous inserts
Related
I’ve a table that looks like this:
Table A
Version,id
5060586,22285
5074515,22701
5074515,22285
7242751,22701
7242751,22285
I want to generate a new key called groupId that is inserted as my example below:
Table A
Version,id,groupId
5060586,22285,1
5074515,22701,2
5074515,22285,2
7242751,22701,2
7242751,22285,2
I want the groupId to be the same as long as the id's are the same in the different versions. So for example version 5074515 and 7242751 has the same id's so therefor the groupId will be the same. If all the id's aren't the same a new groupId should be added as it has in version 5060586.
How can i solve this specific problem in SQL oracle?
One approach is to create a unique value representing the set of ids in each version, then assign a groupid to the unique values of that, then join back to the original data.
INSERT ALL
INTO t (version,id) VALUES (5060586,22285)
INTO t (version,id) VALUES (5074515,22701)
INTO t (version,id) VALUES (5074515,22285)
INTO t (version,id) VALUES (7242751,22701)
INTO t (version,id) VALUES (7242751,22285)
SELECT 1 FROM dual;
WITH groups
AS
(
SELECT version
, LISTAGG(id,',') WITHIN GROUP (ORDER BY id) AS group_text
FROM t
GROUP BY version
),
groupids
AS
(
SELECT group_text, ROW_NUMBER() OVER (ORDER BY group_text) AS groupid
FROM groups
GROUP BY group_text
)
SELECT t.*, groupids.groupid
FROM t
INNER JOIN groups ON t.version = groups.version
INNER JOIN groupids ON groups.group_text = groupids.group_text;
dbfiddle.uk
You can use:
UPDATE tableA t
SET group_id = ( SELECT COUNT(DISTINCT id)
FROM TableA x
WHERE x.Version <= t.version );
Which, for the sample data:
CREATE TABLE TableA (
Version NUMBER,
id NUMBER,
group_id NUMBER
);
INSERT INTO TableA (Version, id)
SELECT 5060586,22285 FROM DUAL UNION ALL
SELECT 5074515,22701 FROM DUAL UNION ALL
SELECT 5074515,22285 FROM DUAL UNION ALL
SELECT 7242751,22701 FROM DUAL UNION ALL
SELECT 7242751,22285 FROM DUAL;
Then, after the update:
SELECT * FROM tablea;
Outputs:
VERSION
ID
GROUP_ID
5060586
22285
1
5074515
22701
2
5074515
22285
2
7242751
22701
2
7242751
22285
2
db<>fiddle here
I have a table that has 3 columns.
create table myTable
(
ID int Primary key,
Detail_ID int references myTable(ID) null, -- reference to self
Master_Value varchar(50) -- references to master table
)
this table has the follow records:
insert into myTable select 100,null,'aaaa'
insert into myTable select 101,100,'aaaa'
insert into myTable select 102,101,'aaaa'
insert into myTable select 103,102,'aaaa' ---> last record
insert into myTable select 200,null,'bbbb'
insert into myTable select 201,200,'bbbb'
insert into myTable select 202,201,'bbbb' ---> last record
the records is saved In the form of relational with ID and Detail_ID columns.
I need to select the last record each Master_Value column. follow output:
lastRecordID Master_Value Path
202 bbbb 200=>201=>202
103 aaaa 100=>101=>102=>103
tips:
The records are not listed in order in the table.
I can not use the max(ID) keyword. beacuse data is not sorted.(may
be the id column updated manually.)
attempts:
I was able to Prepare follow query and is working well:
with Q as
(
select ID ,Detail_ID, Master_Value , 1 RowOrder, CAST(id as varchar(max)) [Path] from myTable where Detail_ID is null
union all
select R.id,R.Detail_ID , r.Master_Value , (q.RowOrder + 1) RowOrder , (q.[Path]+'=>'+CAST(r.id as varchar(max))) [Path] from myTable R inner join Q ON Q.ID=R.Detail_ID --where r.Dom_ID_RowType=1010
)
select * into #q from Q
select Master_Value, MAX(RowOrder) lastRecord into #temp from #Q group by Master_Value
select
q.ID lastRecordID,
q.Master_Value,
q.[Path]
from #temp t
join #q q on q.RowOrder = t.lastRecord
where
q.Master_Value = t.Master_Value
but I need to simple way (one select) and optimal method.
Can anyone help me?
One method uses a correlated subquery to get the last value (which is how I interpreted your question):
select t.*
from mytable t
where not exists (select 1
from mytable t2
where t2.master_value = t.master_value and
t2.id = t.detail_id
);
This returns rows that are not referred to by another row.
For the path, you need a recursive CTE:
with cte as (
select master_value, id as first_id, id as child_id, convert(varchar(max), id) as path, 1 as lev
from mytable t
where detail_id is null
union all
select cte.master_value, cte.first_id, t.id, concat(path, '->', t.id), lev + 1
from cte join
mytable t
on t.detail_id = cte.child_id and t.master_value = cte.master_value
)
select cte.*
from (select cte.*, max(lev) over (partition by master_value) as max_lev
from cte
) cte
where max_lev = lev
Here is a db<>fiddle.
I have below table
CREATE TABLE run_progress
(
id INT PRIMARY KEY,
user varchar(255),
progress numeric
)
INSERT INTO run_progress ( id, user, progress ) VALUES ( 1, 1, 100 )
INSERT INTO run_progress ( id, user, progress ) VALUES ( 2, 2, 90 )
INSERT INTO run_progress ( id, user, progress ) VALUES ( 3, 3, 60 )
INSERT INTO run_progress ( id, user, progress ) VALUES ( 4, 4, 10 )
I want to know user:4 progress compare to rest of the users in the table.
User:4 made a 10% progress, is it possible to know his progress compare to others progress in the table in a global view point?
this is to know how far he is behind or forward compare to the rest of the users.
thank you.
You can aggregate and compare summary statistics in one row:
select max(progress) filter (where id = 4) as user_4,
min(progress) filter (where id <> 4) as min_other_users,
max(progress) filter (where id <> 4) as max_other_users,
avg(progress) filter (where id <> 4) as avg_other_users
from run_progress p
Wouldn't a window sum fit your need?
select *
from (
select
p.*,
avg(progress) filter(where id <> 4) over() avg_progress_of_other_users
from run_progress p
) p
where id = 4
If you want this for all users at once (not only for one specific user), then a lateral join is a better fit:
select p.*, a.*
from run_progress p
left join lateral (
select avg(p1.progress) avg_progress_of_other_users
from run_progress p1
where p1.id <> p.id
) a on true
I got that table :
id table_id ....
1 1
2 1
3 1
4 2
5 2
I want a query to get top record from all who have the same table_id
Result :
id table_id ....
1 1
4 2
I tried :
SELECT
id, shift_id, name_of_shift, person_in_shift,
starttime_in_shift, endtime_in_shift, table_id,
startdate, enddate, point_id
FROM
sarcshifttable
WHERE
(table_id IN (SELECT DISTINCT table_id
FROM sarcshifttable AS sarcshifttable_1))
but its bring me all the data and I want just the top one
He said he wanted all the columns back, so it will have to be this:
SELECT
id, shift_id, name_of_shift, person_in_shift,
starttime_in_shift, endtime_in_shift, table_id,
startdate, enddate, point_id
FROM
sarcshifttable
WHERE id IN
(
SELECT MIN(id)
FROM sarcshifttable
GROUP BY table_id
)
SELECT * FROM Table
WHERE ID IN
(SELECT ID FROM ( SELECT MIN(ID)as ID,table_id FROM Table GROUP BY table_id)x)
They're right - by selecting the lowest (MIN()) ID grouping by table_id, you can find out the earliest row for that particular table ID.
Something like this:
SELECT
MIN(id), table_id
FROM
table1
GROUP BY
table_id
Update:
Better understanding the request, the following SQL will work:
SELECT
id, shift_id, name_of_shift, person_in_shift,
starttime_in_shift, endtime_in_shift, table_id,
startdate, enddate, point_id
FROM
table1
WHERE
id IN (
SELECT MIN(ID)
FROM table1 GROUP BY table_id
)
A working example (updated) can be seen here at SQLFiddle.
DECLARE #What TABLE
(
id INT,
tableId INT
)
INSERT INTO #What
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(5, 2)
SELECT a.id, a.tableId
FROM
(
SELECT id, tableId, Rank() OVER (PARTITION BY tableId ORDER BY id) localRank
FROM #What
) a
WHERE a.localRank = 1
ORDER BY a.id
id tableId
1 1
4 2
I have the following table structure:
Id, Message
1, John Doe
2, Jane Smith
3, Error
4, Jane Smith
Is there a way to get the error record and the surrounding records? i.e. find all Errors and the record before and after them.
;WITH numberedlogtable AS
(
SELECT Id,Message,
ROW_NUMBER() OVER (ORDER BY ID) AS RN
FROM logtable
)
SELECT Id,Message
FROM numberedlogtable
WHERE RN IN (SELECT RN+i
FROM numberedlogtable
CROSS JOIN (SELECT -1 AS i UNION ALL SELECT 0 UNION ALL SELECT 1) n
WHERE Message='Error')
WITH err AS
(
SELECT TOP 1 *
FROM log
WHERE message = 'Error'
ORDER BY
id
),
p AS
(
SELECT TOP 1 l.*
FROM log
WHERE id <
(
SELECT id
FROM err
)
ORDER BY
id DESC
)
SELECT TOP 3 *
FROM log
WHERE id >
(
SELECT id
FROM p
)
ORDER BY
id
Adapt this routine to pick out your target.
DECLARE #TargetId int
SET #TargetId = 3
select *
from LogTable
where Id in (-- "before"
select max(Id)
from LogTable
where Id < #TargetId
-- target
union all select #TargetId
-- "after"
union all select min(Id)
from LogTable
where Id > #TargetId)
select id,messag from
(Select (Row_Number() over (order by ID)) as RNO, * from #Temp) as A,
(select SubRNO-1 as A,
SubRNO as B,
SubRNO+1 as C
from (Select (Row_Number() over (order by ID)) as SubRNO, * from #Temp) as C
where messag = 'Error') as B
where A.RNO = B.A or A.RNO = B.B or A.RNO = B.C
;WITH Logs AS
(
SELECT ROW_NUMBER() OVER (ORDER BY id), id, message as rownum FROM LogTable lt
)
SELECT curr.id, prev.id, next.id
FROM Logs curr
LEFT OUTER JOIN Logs prev ON curr.rownum+1=prev.rownum
RIGHT OUTER JOIN Logs next ON curr.rownum-1=next.rownum
WHERE curr.message = 'Error'
select id, message from tbl where id in (
select id from tbl where message = "error"
union
select id-1 from tbl where message = "error"
union
select id+1 from tbl where message = "error"
)
Get fixed number of rows before & after target
Using UNION for a simple, high performance query (I found selected answer WITH query above to be extremely slow)
Here is a high performance alternative to the WITH top selected answer, when you know an ID or specific identifier for a given record, and you want to select a fixed number of records BEFORE and AFTER that record. Requires a number field for ID, or something like date that can be sorted ascending / descending.
Example: You want to select the 10 records before and after a specific error was recorded, you know the error ID, and can sort by date or ID.
The following query gets (inclusive) the 1 result above, the identified record itself, and the 1 record below. After the UNION, the results are sorted again in descending order.
SELECT q.*
FROM(
SELECT TOP 2
id, content
FROM
the_table
WHERE
id >= [ID]
ORDER BY id ASC
UNION
SELECT TOP 1
id, content
FROM
the_table
WHERE
id < [ID]
ORDER BY id DESC
) q
ORDER BY q.id DESC