Find first available empty column in a row - sql

I have MSSQL table having columns, serves as storage for items for user with certain name. Each user have one record in table Items that looks like that:
Name(string) 1(string) 2(string) 3(string) 4(string) 5(string)
When inserting data, I would need to use the column ("packet") ona by one. Meaning if "1" contains data, use column "2". If "1" is empty, use "1". If only "3" is empty, use "3"/
In other words, I always need to select the lowest empty column to write the data in. If none of these 5 is empty, then do not write data at all.
Could anyone help me with that query? Thank you

The correct way to model this in SQL would be:
CREATE TABLE Tab (
Name varchar(20) not null, --20? Who knows, not specced in question
Idx int not null,
Value varchar(20) not null, --20? Who knows, not specced in question
constraint PK_Tab PRIMARY KEY (Name,Idx),
constraint CK_Tab_Indexes CHECK (Idx between 1 and 5)
)
You could then write your insert as:
INSERT INTO Tab (Name,Idx,Value)
SELECT #Name,COALESCE((select MAX(Idx)+1 from Tab where Name=#Name),1),#Value
where #Name and #Value are the supplied values to insert. If there are already 5 items in the table for a particular name, you'll get a check constraint error and the insert will fail.
This also makes querying for particular values possible, without having to search 5 different columns, and it means that extending to more items in the future far easier (by just changing the check constraint)
I'd had the impression that this was insert only. If deletions are occurring, and you want to reuse items, then the insert query would be:
;WITH Nums(n) as (select 1 union all select 2 union all select 3 union all select 4 union all select 5)
INSERT INTO Tab (Name,Idx,Value)
SELECT #Name,(select MIN(n) from Nums where not exists (select * from Tab where Name=#Name and Idx = n)),#Value
which will now fail because of a not-null constraint once you're up at 5 items. It could be simpler if you already have a numbers table in your database, but I've assumed not above.

Related

How to prevent insertion of value if value exists in other columns

Given 3 columns in a table tblEmails,
email1, nvarchar(50), NULLs not permitted
email2, nvarchar(50), NULLs permitted
email3, nvarchar(50), NULLs permitted
how do I prevent insertions or updates of a (non-NULL) value to any of the three columns if the value already exists in any of the other columns?
I was hoping to apply a CONSTRAINT by checking if the UNION ALL of the three tables contains the value to be inserted, but it seems count() can't be used in CONSTRAINTs.
Any solution implementable via the SSMS gui would be ideal.
I looked through at least a dozen SO posts, some SE posts, and articles online, but could not find a solution (or one that I could understand).
I would suggest creating a function which is then called by the check constraint. Following an example:
CREATE FUNCTION dbo.fn_chkMail(#mail nvarchar(100)) RETURNS INT AS
BEGIN
RETURN (SELECT COUNT(*) FROM mails WHERE mail1 = #mail) + (SELECT COUNT(*) FROM mails WHERE mail2 = #mail) + (SELECT COUNT(*) FROM mails WHERE mail3 = #mail)
END
and then
ALTER TABLE dbo.mails WITH CHECK ADD CONSTRAINT [CK_mails]
CHECK ((dbo.fn_chkMail([mail1]))+(dbo.fn_chkMail([mail2]))+(dbo.fn_chkMail([mail3]))=1)
See fiddle for details: http://sqlfiddle.com/#!18/6f375/7/1
You want to prevent modifying values using update when values are already in the table. Unfortunately, that suggests a trigger.
I think the logic looks like this:
CREATE TRIGGER trg_tblEmails_update ON tblEmails
AFTER UPDATE
AS BEGIN
IF (EXISTS (SELECT 1
FROM inserted i JOIN
deleted d
ON i.<primary key> = d.<primary key>
WHERE (d.email1 IS NOT NULL OR
d.email2 IS NOT NULL OR
d.email3 IS NOT NULL
) AND
(COALESCE(d.email1, '') <> COALESCE(i.email1, '') OR
COALESCE(d.email2, '') <> COALESCE(i.email2, '') OR
COALESCE(d.email3, '') <> COALESCE(i.email3, '')
)
)
)
BEGIN
RAISERROR('You can not update emails when value is already present', 16, 1);
ROLLBACK TRANSACTION;
END;
END;
I would suggest though that there might be a simpler data model. For instance, I would recommend storing the emails in a separate table with one row per email. You would use this table as:
When you insert a value into one email, you insert all three.
You have a unique index on the entity id and email number.
You don't allow updates on the table.
EDIT:
I suspect that you really want a unique constraint. You are not looking within a row but across all rows.
If that is the case, you simply have the wrong data model. You need a table with one email per row. This might require a column to identify which email, but something like this:
create table entity_emails (
entity_email_id int identity(1, 1) primary key,
which_email int,
email varchar(255)
);
Then you want the following constraints:
check (which_email in (1, 2, 3));
unique (entity_id, which_email);
unique (email);
The first two limits the number of emails to 3 per entity. The third insists that the email be unique over all rows and entities.
With the right data model, what you need to do may not require a trigger.

UPDATE two columns with new value under large size table

We have table like :
mytable (pid, string_value, int_value)
This table has more than 20M rows in total. Now we have a feature try to mark all the rows from this tables as invalid. So we need update the table columns: string_Value = NULL and int_value = 0 which indicate this is invalid row ( we still want to keep the pid as it is important to us)
So what is the best way?
I use the following SQL:
UPDATE Mytable
SET string_value = NULL,
int_value = 0;
but this query takes more than 4 minutes in my test env. Is there any better way we can improve it?
Updating all the rows can be quite expensive. Often, it is faster to empty the table and reload it.
In generic SQL this looks like:
create table mytable_temp as
select pid
from mytable;
truncate table mytable; -- back it up first!
insert into mytable (pid, string_value, int_value)
select pid, null, 0
from mytable_temp;
The creation of the temporary table may use different syntax, depending on our database.
Updates can take time to complete. Another way of achieving this is to follow the following steps:
Add new columns with the values you need set as the default value
Drop the original columns
Rename the new columns with the names of the original columns.
You can then drop the default values on the new columns.
This needs to be tested as different DBMSs allow different levels of table alters (i.e. not all DMBSs allow a drop default or a drop column).

Postgres: SELECT or INSERT in high concurrent write load DB

We have a DB for which we need a "selsert" (not upsert) function.
The function should take a text value and return a id column of existing row (SELECT) or insert the value and return id of new row (INSERT).
There are multiple processes that will need to perform this functionality (selsert)
I have been experimenting with pg_advisory_lock and ON CONFLICT clause for INSERT but am still not sure what approach would work best (even when looking at some of the other answers).
So far I have come up with following
WITH
selected AS (
SELECT id FROM test.body_parts WHERE (lower(trim(part))) = lower(trim('finger')) LIMIT 1
),
inserted AS (
INSERT INTO test.body_parts (part)
SELECT trim('finger')
WHERE NOT EXISTS ( SELECT * FROM selected )
-- ON CONFLICT (lower(trim(part))) DO NOTHING -- not sure if this is needed
RETURNING id
)
SELECT id, 'inserted' FROM inserted
UNION
SELECT id, 'selected' FROM selected
Will above query (within function) insure consistency in high
concurrency write workloads?
Are there any other issues I must consider (locking?, etc, etc)
BTW, I can insure that there are no duplicate values of (part) by creating unique index. That is not an issue. What I am after is that SELECT returns existing value if another process does INSERT (I hope I am explaining this right)
Unique index would have following definition
CREATE UNIQUE INDEX body_parts_part_ux
ON test.body_parts
USING btree
(lower(trim(part)));

Is there any editable Auto-Increment besdies IDENTITY?

The reason I need this for is that I made a column on my table called display_order, for now it's smallint and the numbers were pre-determined.
However, when I insert a new record with my software I don't know how to get the highest number in that column and add 1, so I thought about the possibility of an auto-incremented column where if I change 8 to 9 it will change everything else accordingly.
Is this possible?
The answer to your question is "No" IDENTITY is the only auto incrementing capability (and these columns are not updatable)
But if this is a display_order field can't you just make it float to allow you to insert items between other items rather than having to shift all other items down to create a gap?
However, when I insert a new record with my software I don't know how to get the highest number in that column and add 1,
Insert MyTable( display_order, .... )
Select (
Select Max(display_order) + 1
From MyTable As T1
), ...
From MyTable
However, I wouldn't recommend this. If display_order is user settable, then I would simply assume relative values. Thus, it wouldn't matter if a user added two values with a display_order = 0. If you really want to go the extra mile and provide the ability to resequence the display_order, you could do it like so:
Update MyTable
Set display_order = Z.NewSeq
From (
Select PKCol
, Row_Number() Over ( Order By display_order ) As NewSeq
From MyTable
) As Z
Join MyTable As T
On T.PKCol = Z.PKCol
Because you only get one IDENTITY column per table, I would probably use a trigger or other mechanism (if there's a centralized insertion stored proc) to default it to one more than the highest number in the table if not provided. This avoids having to SET IDENTITY_INSERT or anything like that.

SQL query select from table and group on other column

I'm phrasing the question title poorly as I'm not sure what to call what I'm trying to do but it really should be simple.
I've a link / join table with two ID columns. I want to run a check before saving new rows to the table.
The user can save attributes through a webpage but I need to check that the same combination doesn't exist before saving it. With one record it's easy as obviously you just check if that attributeId is already in the table, if it is don't allow them to save it again.
However, if the user chooses a combination of that attribute and another one then they should be allowed to save it.
Here's an image of what I mean:
So if a user now tried to save an attribute with ID of 1 it will stop them, but I need it to also stop them if they tried ID's of 1, 10 so long as both 1 and 10 had the same productAttributeId.
I'm confusing this in my explanation but I'm hoping the image will clarify what I need to do.
This should be simple so I presume I'm missing something.
If I understand the question properly, you want to prevent the combination of AttributeId and ProductAttributeId from being reused. If that's the case, simply make them a combined primary key, which is by nature UNIQUE.
If that's not feasible, create a stored procedure that runs a query against the join for instances of the AttributeId. If the query returns 0 instances, insert the row.
Here's some light code to present the idea (may need to be modified to work with your database):
SELECT COUNT(1) FROM MyJoinTable WHERE AttributeId = #RequestedID
IF ##ROWCOUNT = 0
BEGIN
INSERT INTO MyJoinTable ...
END
You can control your inserts via a stored procedure. My understanding is that
users can select a combination of Attributes, such as
just 1
1 and 10 together
1,4,5,10 (4 attributes)
These need to enter the table as a single "batch" against a (new?) productAttributeId
So if (1,10) was chosen, this needs to be blocked because 1-2 and 10-2 already exist.
What I suggest
The stored procedure should take the attributes as a single list, e.g. '1,2,3' (comma separated, no spaces, just integers)
You can then use a string splitting UDF or an inline XML trick (as shown below) to break it into rows of a derived table.
Test table
create table attrib (attributeid int, productattributeid int)
insert attrib select 1,1
insert attrib select 1,2
insert attrib select 10,2
Here I use a variable, but you can incorporate as a SP input param
declare #t nvarchar(max) set #t = '1,2,10'
select top(1)
t.productattributeid,
count(t.productattributeid) count_attrib,
count(*) over () count_input
from (select convert(xml,'<a>' + replace(#t,',','</a><a>') + '</a>') x) x
cross apply x.x.nodes('a') n(c)
cross apply (select n.c.value('.','int')) a(attributeid)
left join attrib t on t.attributeid = a.attributeid
group by t.productattributeid
order by countrows desc
Output
productattributeid count_attrib count_input
2 2 3
The 1st column gives you the productattributeid that has the most matches
The 2nd column gives you how many attributes were matched using the same productattributeid
The 3rd column is how many attributes exist in the input
If you compare the last 2 columns and the counts
match - you can use the productattributeid to attach to the product which has all these attributes
don't match - then you need to do an insert to create a new combination