How to store and extract number from binary field? - tsql

I have a procedure that is doing INSERT/DELETE operations over table and a trigger which is logging the changes in second table.
What I need is to pass the security user id that instantiated the procedure call to the trigger in order to record who user has made the changes.
It seems that the only way to do this is to use SET CONTEXT_INFO and CONTEXT_INFO().
The issue is, I am not able to extract the number(the user id). For example, the code below:
SET CONTEXT_INFO 10001
GO
SELECT CAST(CONTEXT_INFO() AS INT)
returns 0.

Context info is binary/varbinary, so you may try cast to binary first, and to int then:
SELECT CAST(CAST(CONTEXT_INFO() AS BINARY(4)) as INT)

Related

Is INSTEAD OF UPDATE trigger the best option

I have to check when a table is inserted to/updated to see if a column value exists for the same HotelID and different RoomNo in the same table. I'm thinking that an INSTEAD OF trigger on the table would be a good option, but I read that it's a bad idea to update/insert the table the trigger executes on inside the trigger and you should create the trigger on a view instead (which raises more questions for me)
Is it ok to create a trigger like this? Is there a better option?
CREATE TRIGGER dbo.tgr_tblInterfaceRoomMappingUpsert
ON dbo.tblInterfaceRoomMapping
INSTEAD OF INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #txtRoomNo nvarchar(20)
SELECT #txtRoomNo = Sonifi_RoomNo
FROM dbo.tblInterfaceRoomMapping r
INNER JOIN INSERTED i
ON r.iHotelID = i.iHotelID
AND r.Sonifi_RoomNo = i.Sonifi_RoomNo
AND r.txtRoomNo <> i.txtRoomNo
IF #txtRoomNo IS NULL
BEGIN
-- Insert/update the record
END
ELSE
BEGIN
-- Raise error
END
END
GO
So it sounds like you only want 1 row per combo of HotelID and Sonifi_RoomNo.
CREATE UNIQUE INDEX UQ_dbo_tblInterfaceRoomMapping
ON dbo.tblInterfaceRoomMapping(HotelID,Sonifi_RoomNo)
Now if you try and put a second row with the same values, it will bark at you.
It's (usually) not okay to create a trigger like that.
Your trigger assumes a single row update or insert will only ever occur - is that guaranteed?
What will be the value of #txtRoomNo if multiple rows are inserted or updated in the same batch?
Eg, if an update is performed against the table resulting in 1 row with correct data and 1 row with incorrect data, how do you think your trigger would cope in that situation? Remember triggers fire once per insert/update, not per row.
Depending on your requirments you could keep the instead of trigger concept, however I would suggest a separate trigger for inserts and for updates.
In each you can then insert / update and include a where not exists clause to only allow valid inserts / updates, ignoring inserting or updating anything invalid.
I would avoid raising an error in the trigger, if you need to handle bad data you could also insert into some logging table with the reverse where exists logic and then handle separately.
Ultimately though, it would be best for the application to check if the roomNo is already used.

postgres make a records chain in one table

I have a table in postgres 9.4 and I need to do the following:
When new inserted record comes, I need to find previous records with given parameters and assign it's 'next_message' column value to newly generated record. So I want each record had reference to the next one with given filter, for example 'session_id'. So, if session_id=5, all records with seesion_id=5 should reference next one. I created a trigger that selects previous record and set this field. But it's bad and it will not work in highly loaded db table. How to do that?
That's my trigger:
CREATE OR REPLACE FUNCTION "public"."messages_compute_next_message" () RETURNS trigger
VOLATILE
AS $dbvis$
DECLARE previous_session_message integer;
BEGIN
/*NEW.next_message_id=NEW.id;*/
update message set next_message_id=NEW.id where id=(select max(c.id) from message c where c.session_id=NEW.session_id and c.id<>NEW.id);
RETURN NEW;
END
$dbvis$ LANGUAGE plpgsql
If I post records too frequently, I get many null values in next_message_id fields. And it's logical, otherwise I have to block entire table on every insert. How to do that properly in postgres?
I'd say forget about next_message_id.
The way your trigger looks, it seems to me that messages are ordered by id within one session.
So if you need the previous message for the message with id 42, you can find it with
SELECT max(prev.id)
FROM message prev JOIN message curr
ON prev.session_id = curr.session_id
AND prev.id < curr.id
WHERE curr.id = 42;
Setting the right indexes will speed this up considerably.
I assume there already is an index on id; maybe a second index ON (session_id, id) will help.

How to implement data authorization logic inside a Posgres db?

We are building a web app that sits on top of a postgres db. We would like to implement authorization logic inside the database so that it is opaque to the app. For example, suppose a server side controller requests all users from a view v_user. We would like for the db to handle the authorization of which users the currently logged in user can or cannot see. Obviously the server is going to need to send over the login_pkey (user_pkey of logged in user) on every request for this to work.
The issue we are having is with reads. We were able to do this for inserts, updates and deletes by putting the logic in the triggers behind those operations on all views. The issue we are having is how to do this for reads. How can we include variable logic (i.e. logic that depends on which login_pkey is passed) in a view (or some other place) and how can we pass this information for each query.
If it is important, the server we are using is Node and the ORM is Sequelize.
Thanks in advance.
Ideally you really want row security to do this well. It's available in the 9.5 version in beta now.
But you can do what you need without.
To pass a user identity you can use a custom variable, e.g.
SET myapp.appuser = 'fred';
then access it with current_setting e.g.
SELECT current_setting('myapp.appuser')
This will raise an ERROR if the setting does not exist, so you should set a default blank value in postgresql.conf, with ALTER DATABASE SET, etc. Or use PostgreSQL 9.5's current_setting('settingname', true) to return null on missing values.
To filter what users can see, use views that check the user identity setting your app sets at connect-time, per the above.
This is not safe if your users can run arbitrary SQL, because nothing stops them RESETing the setting or doing a SET myapp.appuser = 'the-admin'.
It's very easy to implement this using Pl/Python global dict GD. First, you need to write auth() function:
create or replace function auth(login text, pass text) as $$
-- Check auth login here
GD['user_id'] = get_user_id_by_login(login)
$$ language plpythonu;
Then you have to write get_current_user() function
create or replace function get_current_user() returns integer as $$
return GD['user_id']
$$ langugage plpythonu;
Now, you can get current user any time you want. For example:
-- inside stored procedure
vUserId := get_current_user()
-- in query
select * from some_table where owner_id = get_current_user()
Remember, that GD is stored per session, so, as you wrote, you need to login every time you connect to database. In my ORM I do like this:
class MyORM():
def login(self, user, password):
cursor = self.__conn.cursor()
result = cursor.execute('select core.login(%s, %s)', (user, password,))
data = cursor.fetchone()
cursor.close()
return data[0]
def auth(self, cursor):
cursor.execute('select core.auth(%s)', (g.user_id,))
def query(self, query):
cursor = self.__conn.cursor()
self.auth(cursor)
cursor.execute(query)
data = cursor.fetchall()
cursor.close()
return data

PostgreSQL - next serial value in a table

I have a simple question, suppose we have a table:
id A B
1 Jon Doe
2 Foo Bar
Is there a way to know, which is the next id's increment, in this case 3 ?
Database is PostgreSQL!
Tnx alot!
If you want to claim an ID and return it, you can use nextval(), which advances the sequence without inserting any data.
Note that if this is a SERIAL column, you need to find the sequence's name based on the table and column name, as follows:
Select nextval(pg_get_serial_sequence('my_table', 'id')) as new_id;
There is no cast-iron guarantee that you'll see these IDs come back in order (the sequence generates them in order, but multiple sessions can claim an ID and not use it yet, or roll back an INSERT and the ID will not be reused) but there is a guarantee that they will be unique, which is normally the important thing.
If you do this often without actually using the ID, you will eventually use up all the possible values of a 32-bit integer column (i.e. reach the maximum representable integer), but if you use it only when there's a high chance you will actually be inserting a row with that ID it should be OK.
To get the current value of a sequence without affecting it or needing a previous insert in the same session, you can use;
SELECT last_value FROM tablename_fieldname_seq;
An SQLfiddle to test with.
Of course, getting the current value will not guarantee that the next value you'll get is actually last_value + 1 if there are other simultaneous sessions doing inserts, since another session may have taken the serial value before you.
SELECT currval('names_id_seq') + 1;
See the docs
However, of course, there's no guarantee that it's going to be your next value. What if another client grabs it before you? You can though reserve one of the next values for yourself, selecting a nextval from the sequence.
I'm new so here's the process I use having little to no prior knowledge of how Postgres/SQL work:
Find the sequence for your table using pg_get_serial_sequence()
SELECT pg_get_serial_sequence('person','id');
This should output something like public.person_id_seq. person_id_seq is the sequence for your table.
Plug the sequence from (1) into nextval()
SELECT nextval('person_id_seq');
This will output an integer value which will be the next id added to the table.
You can turn this into a single command as mentioned in the accepted answer above
SELECT nextval(pg_get_serial_sequence('person','id'));
If you notice that the sequence is returning unexpected values, you can set the current value of the sequence using setval()
SELECT setval(pg_get_serial_sequence('person','id'),1000);
In this example, the next call to nextval() will return 1001.

Sequence Generators in T-SQL

We have an Oracle application that uses a standard pattern to populate surrogate keys. We have a series of extrinsic rows (that have specific values for the surrogate keys) and other rows that have intrinsic values.
We use the following Oracle trigger snippet to determine what to do with the Surrogate key on insert:
IF :NEW.SurrogateKey IS NULL THEN
SELECT SurrogateKey_SEQ.NEXTVAL INTO :NEW.SurrogateKey FROM DUAL;
END IF;
If the supplied surrogate key is null then get a value from the nominated sequence, else pass the supplied surrogate key through to the row.
I can't seem to find an easy way to do this is T-SQL. There are all sorts of approaches, but none of which use the notion of a sequence generator like Oracle and other SQL-92 compliant DBs do.
Anybody know of a really efficient way to do this in SQL Server T-SQL? By the way, we're using SQL Server 2008 if that's any help.
You may want to look at IDENTITY. This gives you a column for which the value will be determined when you insert the row.
This may mean that you have to insert the row, and determine the value afterwards, using SCOPE_IDENTITY().
There is also an article on simulating Oracle Sequences in SQL Server here: http://www.sqlmag.com/Articles/ArticleID/46900/46900.html?Ad=1
Identity is one approach, although it will generate unique identifiers at a per table level.
Another approach is to use unique identifiers, in particualr using NewSequantialID() that ensues the generated id is always bigger than the last. The problem with this approach is you are no longer dealing with integers.
The closest way to emulate the oracle method is to have a separate table with a counter field, and then write a user defined function that queries this field, increments it, and returns the value.
Here is a way to do it using a table to store your last sequence number. The stored proc is very simple, most of the stuff in there is because I'm lazy and don't like surprises should I forget something so...here it is:
----- Create the sequence value table.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SequenceTbl]
(
[CurrentValue] [bigint]
) ON [PRIMARY]
GO
-----------------Create the stored procedure
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure [dbo].[sp_NextInSequence](#SkipCount BigInt = 1)
AS
BEGIN
BEGIN TRANSACTION
DECLARE #NextInSequence BigInt;
IF NOT EXISTS
(
SELECT
CurrentValue
FROM
SequenceTbl
)
INSERT INTO SequenceTbl (CurrentValue) VALUES (0);
SELECT TOP 1
#NextInSequence = ISNULL(CurrentValue, 0) + 1
FROM
SequenceTbl WITH (HoldLock);
UPDATE SequenceTbl WITH (UPDLOCK)
SET CurrentValue = #NextInSequence + (#SkipCount - 1);
COMMIT TRANSACTION
RETURN #NextInSequence
END;
GO
--------Use the stored procedure in Sql Manager to retrive a test value.
declare #NextInSequence BigInt
exec #NextInSequence = sp_NextInSequence;
--exec #NextInSequence = sp_NextInSequence <skipcount>;
select NextInSequence = #NextInSequence;
-----Show the current table value.
select * from SequenceTbl;
The astute will notice that there is a parameter (optional) for the stored proc. This is to allow the caller to reserve a block of ID's in the instance that the caller has more than one record that needs a unique id - using the SkipCount, the caller need make only a single call for however many IDs are needed.
The entire "IF EXISTS...INSERT INTO..." block can be removed if you remember to insert a record when the table is created. If you also remember to insert that record with a value (your seed value - a number which will never be used as an ID), you can also remove the ISNULL(...) portion of the select and just use CurrentValue + 1.
Now, before anyone makes a comment, please note that I am a software engineer, not a dba! So, any constructive criticism concerning the use of "Top 1", "With (HoldLock)" and "With (UPDLock)" is welcome. I don't know how well this will scale but this works OK for me so far...