Can you tell SQL Server not to return specific rows? - sql

We have a database where contacts are only soft-deleted, i.e. we set a bit named deleted to true. The API handles this, but sometimes you need to run queries directly against the database and it's easy to forget that bit, and export contacts that are actually deleted. It's not a big problem, because I'm used to the system, but if I'm on holiday, or if someone else is to take over I'd like to make sure these rows are not returned - unless specifically asked for. This leaves us with the following two questions:
Is there a way to tell SQL Server (2005 in this case) not to return rows if for example a bit is set to true?
If so, can these rows still be retrieved if specifically asked for (where bit = 'true')?

In order to select either the non-deleted rows or the deleted ones, create two views:
CREATE VIEW ActiveContacts
AS
SELECT (list of fields)
FROM dbo.Contacts
WHERE deleted = 0
CREATE VIEW DeletedContacts
AS
SELECT (list of fields)
FROM dbo.Contacts
WHERE deleted = 1
Now, you can easily just pick from either the active or the deleted contacts:
SELECT (list of fields)
FROM ActiveContacts
SELECT (list of fields)
FROM DeletedContacts
That way, you can easily encapsulate that selection criteria and if you don't return that "deleted" flag in your view, outside users won't even see that flag and won't know it's there.
Marc

The only way that I can see to automatically do this for you would be to create views on the relevant tables that specifically exclude the deleted rows. Then use the real tables when you need to see the deleted data.

You could create a view that hides those rows, and if you need the rows directly, you can still go to the table. Try CREATE VIEW vwBlah AS SELECT table, columns, go, here FROM TableName WHERE flag = 0.

Related

Using a Delete query on a single table when referencing other tables

I want to run a delete query to remove certain data from a table in a Sharepoint list using an MS Access query. However I want to be sure only to delete from a single list based on the values of another table.
The table is TMainData: This consists solely of number fields that are references to the keyfields in other tables, such as TProgram which has a program name, or TContact which has the point of contact, or TPositionTitle which has a title like Site Director.
So a TMainData entry looks something like
ProgramID, which links to TPrograms: 4
ContactID, which links to TContacts: 42
PositionTitle, which links to TPositionTitle: 3
This tells me that the Site Director (TPositionTitle 3) of Anesthesiology (ProgramID 4) is John Smith (ContactID 42).
Here's where it gets tricky: I have a reference under TPrograms to TProgramType. I want to delete all records under TMainData where they link to a certain Program Type, because that program type is going away. HOWEVER... I don't want to delete the program itself (yet), just the lines referencing that program in TMainData.
The "manual" way I see to do this is to run queries that identify what the ProgramIDs are of the programs I want to delete the contacts for, and then use those IDs in a delete query that only references the TMainData query. I'm wondering if there's a way to use referential data, because I may have to be running some ridiculous update queries at a later time that would need this same info.
I dug through https://support.office.com/en-us/article/Use-queries-to-delete-one-or-more-records-from-a-database-A323BF1A-C9B4-4C86-8719-BE58BDF1B10C but it doesn't seem to cover deleting from one table based on values referenced in another table.
You already seem to understand what you need to do to achieve the desired result when you state:
...run queries that identify what the ProgramIDs are of the programs I want to delete the contacts for, and then use those IDs in a delete query that only references the TMainData query.
If I've understood your description correctly, I would suggest something along the lines of:
delete from tmaindata
where
tmaindata.programid in
(
select tprograms.programid
from tprograms
where tprograms.tprogramtype = 'YourProgramType'
)
Always take a backup of your data before running delete queries - there is no undo.

How do I give different users access to different rows without creating separate views in BigQuery?

In this question: How do I use row-level permissions in BigQuery? it describes how to use an authorized view to grant access to only a portion of a table. But I'd like to give different users access to different rows. Does this mean I need to create separate views for each user? Is there an easier way?
Happily, if you want to give different users access to different rows in your table, you don't need to create separate views for each one. You have a couple of options.
These options all make use of the SESSION_USER() function in BigQuery, which returns the e-mail address of the currently running user. For example, if I run:
SELECT SESSION_USER()
I get back tigani#google.com.
The simplest option, then, for displaying different rows to different users, is to add another column to your table that is the user who is allowed to see the row. For example, the schema: {customer:string, id:integer} would become {customer:string, id:integer, allowed_viewer: string}. Then you can define a view:
#standardSQL
SELECT customer, id
FROM private.customers
WHERE allowed_viewer = SESSION_USER()
(note, don't forget to authorize the view as described here).
Then I'd be able to see only the fields where tigani#google.com was the value in the allowed_viewer column.
This approach has its own drawbacks, however; You can only grant access to a single user at a time. One option would be to make the allowed_viewer column a repeated field; this would let you provide a list of users for each row.
However, this is still pretty restrictive, and requires a lot of bookkeeping about which users should have access to which row. Chances are, what you'd really like to do is specify a group. So your schema would look like: {customer:string, id:integer, allowed_group: string}, and anyone in the allowed_group would be able to see your table.
You can make this work by having another table that has your group mappings. That table would look like: {group:string, user_name:string}. The rows might look like:
{engineers, tigani#google.com}
{engineers, some_engineer#google.com}
{administrators, some_admin#google.com}
{sales, some_salesperson#google.com}
...
Let's call this table private.access_control. Then we can change our view definition:
#standardSQL
SELECT c.customer, c.id
FROM private.customers c
INNER JOIN (
SELECT group
FROM private.access_control
WHERE SESSION_USER() = user_name) g
ON c.allowed_group = g.group
(note you will want to make sure that there are no duplicates in private.access_control, otherwise it could records to repeat in the results).
In this way, you can manage the groups in the private.access_control separately from the data table (private.customers).
There is still one piece missing that you might want; the ability for groups to contain other groups. You can get this by doing a more complex join to expand the groups in the access control table (you might want to consider doing this only once and saving the results, to save the work each time the main table is queried).

SQL Statement deleting entire table

I have two tables that both have an ID number which are linked together (AllUsers and AllProfiles). When the user presses a button, I want the AllUsers table to check for IDs it has that are not present in the AllProfiles table and delete them. I'm new to SQLCE and hacked this together. The problem is, it keeps deleting the entire table. Why?
DELETE FROM AllUsers
WHERE EXISTS
(SELECT ID
FROM AllUsers
WHERE (ID NOT IN
(SELECT ID
FROM AllProfiles)))
Also, is this an efficient way of bulk deleting thousands of records? I tried Linq's DeleteAllOnSubmit but it was too slow. I'm hoping since the above is comparing two tables directly, it should be efficient.
(I don't want to use cascading as I need control of each table individually)
EDIT - The SELECT statement correctly returns the missing IDs, so there's something wrong with the DELETE FROM AllUsers WHERE EXISTS part.
You're basically saying
delete from allusers
where TRUE -- this is pseudo, but you get the idea
Your original query deletes the whole table because the only condition is boolean... if that returns data then it'll delete all the data. If your exists clause does not return data it would not delete any data.
You want something like this (I'm not fluent with CE, but you should be able to make a minor modification if it doesn't 100% transport over to CE):
delete from allusers
where id not in
(
select id
from allprofiles
)

SQL Server Database - Hidden Fields?

I'm implementing CRUD on my silverlight application, however I don't want to implement the Delete functionality in the traditional way, instead I'd like to set the data to be hidden instead inside the database.
Does anyone know of a way of doing this with an SQL Server Database?
Help greatly appreciated.
You can add another column to the table "deleted" which has value 0 or 1, and display only those records with deleted = 0.
ALTER TABLE TheTable ADD COLUMN deleted BIT NOT NULL DEFAULT 0
You can also create view which takes only undeleted rows.
CREATE VIEW undeleted AS SELECT * FROM TheTable WHERE deleted = 0
And you delete command would look like this:
UPDATE TheTable SET deleted = 1 WHERE id = ...
Extending Lukasz' idea, a datetime column is useful too.
NULL = current
Value = when soft deleted
This adds simple versioning that a bit column can not which may work better
In most situations I would rather archive the deleted rows to an archive table with a delete trigger. This way I can also capture who deleted each row and the deleted rows don't impact my performance. You can then create a view that unions both tables together when you want to include the deleted ones.
You could do as Lukasz Lysik suggests, and have a field that serves as a flag for "deleted" rows, filtering them out when you don't want them showing up. I've used that in a number of applications.
An alternate suggestion would be to add an extra status assignment if there's a pre-existing status code. For example, in a class attendance app we use internally an attendance record could be "Imported", "Registered", "Completed", "Incomplete", etc.* - we added a "Deleted" option for times where there are unintentional duplicates. That way we have a record and we're not just throwing a new column at the problem.
*That is the display name for a numeric code used behind the scenes. Just clarifying. :)
Solution with triggers
If you are friends with DB trigger, then you might consider:
add a DeletedAt and DeletedBy columns to your tables
create a view for each tables (ex: for table Customer have a CustomerView view, which would filter out rows that have DeletedAt not null (idea of gbn with date columns)
all your CRUD operations perform as usual, but not on the Customer table, but on the CustomerView
add INSTEAD OF DELETE trigger that would mark the row as delete instead of physically deleting it.
you may want to do a bit more complex stuff there like ensuring that all FK references to this row are also "logically" deleted in order to still have logical referential integrity
I you choose to use this pattern, I would probably name my tables differently like TCustomer, and views just Customer for clarity of client code.
Be careful with this kind of implementation because soft deletes break referential integrity and you have to enforce integrity in your entities using custom logic.

TSQL update trigger: joining inserted and deleted

I have an on update trigger in SQL Server 2008. I only need to perform the trigger action if certain columns have been modified. Thus I'd like to check what has changed.
T-SQL offers an "if update( columnName )" construct. However, if many rows have been updated and only a single one of them has the particular column value changed "if update()" will have to return true. This'll make me perform the trigger action for far more rows than required.
So instead of using "if update()" I thought I'd just join the virtual deleted and inserted tables (the rows before and after update) and compare the relevant columns myself. However, how can I join the two tables? I cannot use the table's primary key since that may have been modified by the update. The only thing I can think of is joining by row_number(), i.e. implicit table ordering. This feels very wrong though and I don't know whether SQL Server actually offers any guarantuees that rows in inserted are ordered the same as in deleted.
With your design (that allows changing primary keys) it seems very hard to build a consistent logic.
Say, you have this table:
id value
1 2
2 1
and issue this operation:
UPDATE mytable
SET id = CASE WHEN id = 1 THEN 2 ELSE 1 END,
value = CASE WHEN value = 1 THEN 2 ELSE 1 END
which updates both records but leaves the table as this:
id value
2 1
1 2
, which from relational point of view is similar to not changing the table at all.
The whole point of the primary keys is that they never change.
If you use IDENTITY columns as Primary Keys, you don't have the problem of updated PK columns.
to prevent a PK from changing, add this to the top of your trigger:
IF (UPDATE(yourPKcol1) OR UPDATE(yourPKcol2))
BEGIN
RAISERROR('Primary Key change not permitted!',16,1)
ROLLBACK
RETURN
END
Your best bet might be to (as I mentioned in a comment above) create a new table, if possible, that includes all the data in the original but also includes an immutable primary key (IDENTITY works, or you can use something else if you prefer). You can then expose a view of this new table that mimics the name and schema of the original table. This will give you a fixed ID that you can use to track changes as you wish.
All this assumes that a view works adequately in your particular case -- if the app is doing something very weird it might not work properly, but if it's just throwing standard CRUD-style SQL at it it should work fine.
Your trade off is simplicity & maintainability vs performance
if performance is not prioritized use if update(YourTriggerActionColumn) directly
if performance is prioritized then the way to do it is use "if update(PrimaryKeyColumn)" so if primary key didn't change use inserted-deleted join else if the primary key did changed then check "if update(YourTriggerActionColumn)"
since PKs don't often change then most of the time the inserted-deleted join method will be used thus solves your performance problem.
little late but just my 2 cents :)