Auto-incrementing field that depends on another field [duplicate] - sql

This question already has answers here:
Is it possible to use a PG sequence on a per record label?
(4 answers)
Closed 9 years ago.
I have two models, A and B. A has many B. Originally, both A and B had an auto-incrementing primary key field called id, and B had an a_id field. Now I have found myself needing a unique sequence of numbers for each B within an A. I was keeping track of this within my application, but then I thought it might make more sense to let the database take care of it. I thought I could give B a compound key where the first component is a_id and the second component auto-increments, taking into consideration the a_id. So if I insert two records with a_id 1 and one with a_id 2 then I will have something like:
a_id | other_id
1 | 1
1 | 2
2 | 1
If ids with lower numbers are deleted, then the sequence should not recycle these numbers. So if (1, 2) gets deleted:
a_id | other_id
1 | 1
2 | 1
When the next record with a_id 1 is added, the table will look like:
a_id | other_id
1 | 1
2 | 1
1 | 3
How can I do this in SQL? Are there reasons not to do something like this?
I am using in-memory H2 (testing and development) and PostgreSQL 9.3 (production).

The answer to your question is that you would need a trigger to get this functionality. However, you could just create a view that uses the row_number() function:
create view v_table as
select t.*,
row_number() over (partition by a order by id) as seqnum
from table t;
Where I am calling the primary key for the table id.

Related

optimizing child/parent structure in one table with a lot of data

I have a table which has a simple parent child structure
products:
- id
- product_id
- time_created
- ... a few other columns
It is a parent if product_id IS NULL. Product id behaves here like parent_id. Data inside looks like this:
id | product_id
1 NULL
2 1
3 1
4 NULL
4 4
This table is updated every night a new versions are added.
Every user is using a lot of these products but only one version. User is notified if new rows are added for an product_id.
He can stop using id:2 and start using id:3. An another user will continue using id:2 etc.
products table is updated every night and it grows pretty fast. There are around 500000 rows at the moment and every night adds around 20000, probably 5-7000000 changes (new rows) per year.
Is there a way to optimize this database/table structure? Should I change anything? Is it a problem to have so much data in one table?
Your question is not clear. The sample data is suggesting that the parent-child relationship is only one level deep. If so, this is not a particularly hard problem. You can create a query to look up the most recent product id for each product -- and I'm assuming this is the one with the maximum id:
select id, product_id,
max(id) over (partition by coalsesce(product_id, id)) as biggest_id
from table t;
This is then a lookup table, to get the biggest id. It would produce:
id | product_id | biggest_id
1 NULL 3
2 1 3
3 1 3
4 NULL 4
4 4 4
If your table has deeper hierarchies, you can solve the problem using recursive CTEs, or by doing the calculation when the table is updated.

Multiple records in a table matched with a column

The architecture of my DB involves records in a Tags table. Each record in the Tags table has a string which is a Name and a foreign kery to the PrimaryID's of records in another Worker table.
Records in the Worker table have tags. Every time we create a Tag for a worker, we add a new row in the Tags table with the inputted Name and foreign key to the worker's PrimaryID. Therefore, we can have multiple Tags with different names per same worker.
Worker Table
ID | Worker Name | Other Information
__________________________________________________________________
1 | Worker1 | ..........................
2 | Worker2 | ..........................
3 | Worker3 | ..........................
4 | Worker4 | ..........................
Tags Table
ID |Foreign Key(WorkerID) | Name
__________________________________________________________________
1 | 1 | foo
2 | 1 | bar
3 | 2 | foo
5 | 3 | foo
6 | 3 | bar
7 | 3 | baz
8 | 1 | qux
My goal is to filter WorkerID's based on an inputted table of strings. I want to get the set of WorkerID's that have the same tags as the inputted ones. For example, if the inputted strings are foo and bar, I would like to return WorkerID's 1 and 3. Any idea how to do this? I was thinking something to do with GROUP BY or JOINING tables. I am new to SQL and can't seem to figure it out.
This is a variant of relational division. Here's one attempt:
select workerid
from tags
where name in ('foo', 'bar')
group by workerid
having count(distinct name) = 2
You can use the following:
select WorkerID
from tags where name in ('foo', 'bar')
group by WorkerID
having count(*) = 2
and this will retrieve your desired result/
Regards.
This article is an excellent resource on the subject.
While the answer from #Lennart works fine in Query Analyzer, you're not going to be able to duplicate that in a stored procedure or from a consuming application without opening yourself up to SQL injection attacks. To extend the solution, you'll want to look into passing your list of tags as a table-valued parameter since SQL doesn't support arrays.
Essentially, you create a custom type in the database that mimics a table with only one column:
CREATE TYPE list_of_tags AS TABLE (t varchar(50) NOT NULL PRIMARY KEY)
Then you populate an instance of that type in memory:
DECLARE #mylist list_of_tags
INSERT #mylist (t) VALUES('foo'),('bar')
Then you can select against that as a join using the GROUP BY/HAVING described in the previous answers:
select workerid
from tags inner join #mylist on tag = t
group by workerid
having count(distinct name) = 2
*Note: I'm not at a computer where I can test the query. If someone sees a flaw in my query, please let me know and I'll happily correct it and thank them.

Oracle 12c - Insert values in a table using values from another table

I have a table (TABLEA) like so:
type_id level
1 7
2 4
3 2
4 5
And another table (TABLEB) like so:
seq_id type_id name order level
1 1 display 1 7
2 1 header 2
3 1 detail 3
4 2 display 1 4
5 2 header 2
6 2 detail 3
TABLEB.TYPE_ID is FK to TABLEA.TYPE_ID. Currently I am entering the data in TABLEB manually.
I have 2 new rows in TABLEA.. type_id 3 and 4.
How can I populate data which do not exist in TABLEB automatically using TABLEA? I would like all columns in TABLEB to be inserted automatically.
So, as you can see:
SEQ_ID will be sequential
When ORDER value is 1, NAME value will be "display", and LEVEL will be 7
When ORDER value is 2, NAME value will be "header"
When ORDER value is 3, NAME value will be "detail"
I am expecting after the insert:
seq_id type_id name order level
1 1 display 1 7
2 1 header 2
3 1 detail 3
4 2 display 1 4
5 2 header 2
6 2 detail 3
7 3 display 1 2
8 3 header 2
9 3 detail 3
10 4 display 1 5
11 4 header 2
12 4 detail 3
Any help is appreciated!
You can either:
Have your application code populate both tables, i.e.: INSERTs the appropriate records into to both tables.
(Sounds like you're leaning to) Have Oracle do the work behind the scenes, i.e.: Oracle does the INSERTs in TABLEB for you. The way to do this is by creating a TRIGGER on TABLEA. Here's an example that might get you started: https://stackoverflow.com/a/13356277/1680777
Some people will tell you that TRIGGERs might make debugging difficult because part of your logic is in the database. There's some validity to that criticism. I won't say always/never use triggers. Use them where they make sense: where the value they provide outweighs their complexity.
So this can be done in pure SQL: INSERT ALL which allows us to issue multiple inserts in the same statement.
insert all
into tableb (seq_id, type_id, name, order_id, level_id)
values(tableb_id_seq.nextval, type_id, 'display', 1, level_id)
into tableb (seq_id, type_id, name, order_id)
values(tableb_id_seq.nextval+1, type_id, 'header', 2)
into tableb (seq_id, type_id, name, order_id)
values(tableb_id_seq.nextval+2, type_id, 'detail', 3)
select a.type_id, a.level_id from tablea a
minus
select b.type_id, b.level_id from tableb b
/
The manipulation of the sequence is a bit funny: it is required because every call to NEXTVAL returns the same value in one statement. Obviously in order to make this work, the sequence needs to increment by three:
create sequence tableb_id_seq increment by 3;
This might be enough to rule out such an approach approach. As an alternative you could use (SEQ_ID, ORDER_ID) as a compound primary key but that's not nice either.
By the way, ORDER and LEVEL are keywords: you can't use them as column names.

SQL Server ID reseed after deleltion of a middle record

i have a Table from which i delete records .
The problem is when i delete a certain record,its ID flies away too, so the ID order is no longer respected within the table.
What i want is a SQL Server Procedure to rearrange records after the deletion of one of them.
Example :
ID ID ID
1 1 1
2 I delete record 2, i want to have this ===> 2 and NOT this : 3
3 3 4
4 4 5
5
You don't want to do this. The id should be a field that has no meaning other than identifying a row. You might have other tables that refer to the id and they would break.
Instead, just recalculate a sequential value when you query the table:
select t.*, row_number() over (order by id) as seqnum
from t;

Numbering rows without window functions

In Postgresql 8.2 I want to sequentially number rows. I have the table t at SQL Fiddle:
c
---
3
2
I want this:
c | i
---+---
2 | 1
3 | 2
I tried this:
select *
from
(select c from t order by c) s
cross join
generate_series(1, 2) i
And got:
c | i
---+---
2 | 1
3 | 1
2 | 2
3 | 2
The only thing I can think of is a sequence. You could do something like this:
drop sequence if exists row_numbers;
create temporary sequence row_numbers;
select next_val('row_numbers'), dt.c
from (select c from t order by c) as dt;
I'd throw a drop sequence row_numbers in as well but the temporary should take care of that if you forget.
This is a bit cumbersome but you might be able to wrap it in a function to hide some of the ugliness.
Keep in mind that 8.2 is no longer supported but 8.4 is and 8.4 has window functions.
References (8.2 versions):
CREATE SEQUENCE
DROP SEQUENCE
You can use a "triangular join" as in the following:
select a.c, (select count(*) from t where c <= a.c) as i
from t as a
order by i
This assumes, however, that the values of c are unique, as the "row numbering" scheme is simply a count of rows that are less-than-or-equal to the current row. This can be expanded to included a primary key or other unique column for tie-breaking, as necessary.
Also, there can be some performance implications with joining in this manner.