SQL Group By with a condition - asp.net

Scenario: I need to create a report for the auditors for an ASP.Net application. I have a program that parses the XML of the web.config files in a directory for the authorization tag and creates a report providing which users have access to which folder in the site structure. The report is shown below.
UserName, Firstname, LastName, Directory, Roles, Access,
LastLoginDate
Problem: As you can see from the report some directories (the middle column with GISMO in it) show up twice, with both allow and deny for a user. I am wondering if there is a way to group the results in such a way that if there is a row that has allow for a directory then the deny's are not shown but otherwise they are.
Alternatively if this can be manipulated in VB.net/C# that is also an option. It comes back there and is pumped into an Excel spreadsheet.
Any help is appreciated. Thanks in advance.
Edit: I should have explained better. I still need the deny rows to show if the user isn't allowed in the directory. But if they are allowed then there is no point showing the deny rows.

This works on an Oracle database, so it should work or get you close on SQL Server as I know SQL Server
supports the main component of this, the CASE operation.
CREATE TABLE user_permissions (
user_role VARCHAR2(10) NOT NULL,
dir VARCHAR2(10) NOT NULL,
user_access VARCHAR2(5) NOT NULL
);
INSERT INTO user_permissions VALUES ('admin', 'dir1', 'allow');
INSERT INTO user_permissions VALUES ('admin', 'dir2', 'allow');
INSERT INTO user_permissions VALUES ('power', 'dir1', 'allow'); -- Allow and Deny dir1
INSERT INTO user_permissions VALUES ('power', 'dir1', 'deny');
INSERT INTO user_permissions VALUES ('power', 'dir2', 'deny');
COMMIT;
SELECT UNIQUE j.*
FROM (
SELECT user_role, dir,
MAX(CASE user_access WHEN 'allow' THEN 1 ELSE 0 END) allowFlag,
MAX(CASE user_access WHEN 'deny' THEN 1 ELSE 0 END) denyFlag
FROM user_permissions
GROUP BY user_role, dir
) t
JOIN user_permissions j ON (t.user_role = j.user_role AND t.dir = j.dir)
WHERE j.user_access = 'allow' OR (t.allowFlag = 0 and user_access = 'deny');
Results:
USER_ROLE DIR USER_ACCESS
---------- ---------- -----------
admin dir1 allow
admin dir2 allow
power dir1 allow
power dir2 deny
Basically, you use a pivot table to aggregate the multiple rows into a single row describing the attributes for the directory. Once you have the aggregated row, it's easy to compare the attributes you've declared to join up the rows that you want to display.

If you've got SQL Server 2005 or newer, you can use this:
with cte as (
select ROW_NUMBER() OVER (partition by username, directory order by access) as row, *
from report
)
select *
from cte
where row = 1
In the partition clause, put in whatever makes a "group" unique.
Reference:
http://msdn.microsoft.com/en-us/library/ms190766.aspx
http://msdn.microsoft.com/en-us/library/ms186734.aspx

Something like this should work but this assumes that your paths were not entered such as /directory/directory/, /directory/Directory/, directory/directory/default.aspx, etc. Your best bet would be to parse the data and remove duplicates at the .NET process level you've created, since parsing at that stage is usually easier.
select derived.*,
case when exists
(select top 1 1 from table_name as t2 where t2.username = derived.username and t2.directory=derived.directory and t2.access = 'allow') then 1 else 0 end as is_allowed,
case when exists
(select top 1 1 from table_name as t2 where t2.username = derived.username and t2.directory=derived.directory and t2.access = 'deny') then 1 else 0 end as is_denied,
from
(
select distinct t.username, t.firstname, t.lastname, t.directory
from table_name as t
) as derived

SELECT UserName, Firstname, LastName, Directory, Roles, Access, LastLoginDate
FROM Report R
WHERE Access = 'allow'
OR ( Access = 'deny'
AND NOT EXISTS
( SELECT *
FROM Report R2
WHERE R2.Directory = R.Directory
AND R2.UserName = R.UserName
AND R2.Roles = R.Roles
)
)
Based on your comments, this line should be removed, so only (UserName, Directory) combination is checked:
AND R2.Roles = R.Roles

Related

ORA-01427: single-row subquery returns more than one row when inserting multiple rows

I am trying to assign or give all permissions of a user to another given user, 13053 but facing this Oracle error, ORA-01427: single-row subquery returns more than one row and i know exactly which part of my SQL statement shown below is returning this error but failed to handle it because what i want to achieve is to give those multiple rows returned to the given user with an id of 13053.
My attempt
INSERT INTO userpermissions (
userid,permissionid
) VALUES (
13053,( SELECT permissionid
FROM userpermissions
WHERE userid = ( SELECT userid
FROM users
WHERE username = '200376'
)
)
);
Any help ?
Thanks in advance.
A rewrite ought to do the trick:
INSERT INTO USERPERMISSIONS(
USERID,
PERMISSIONID
)
SELECT 13053 AS USERID,
p.PERMISSIONID
FROM USERPERMISSIONS p
WHERE p.userid = (SELECT userid FROM users WHERE username = '200376');
The problem with the original insert is that you are using single-row insert syntax when you are really trying to insert a set of rows.
Including the target userid as a literal is one way to make the set of rows look the way I am assuming you intend.

how to change the auto generate id key in sqlserver [duplicate]

I have SQL Server database and I want to change the identity column because it started
with a big number 10010 and it's related with another table, now I have 200 records and I want to fix this issue before the records increases.
What's the best way to change or reset this column?
You can not update identity column.
SQL Server does not allow to update the identity column unlike what you can do with other columns with an update statement.
Although there are some alternatives to achieve a similar kind of requirement.
When Identity column value needs to be updated for new records
Use DBCC CHECKIDENT which checks the current identity value for the table and if it's needed, changes the identity value.
DBCC CHECKIDENT('tableName', RESEED, NEW_RESEED_VALUE)
When Identity column value needs to be updated for existing records
Use IDENTITY_INSERT which allows explicit values to be inserted into the identity column of a table.
SET IDENTITY_INSERT YourTable {ON|OFF}
Example:
-- Set Identity insert on so that value can be inserted into this column
SET IDENTITY_INSERT YourTable ON
GO
-- Insert the record which you want to update with new value in the identity column
INSERT INTO YourTable(IdentityCol, otherCol) VALUES(13,'myValue')
GO
-- Delete the old row of which you have inserted a copy (above) (make sure about FK's)
DELETE FROM YourTable WHERE ID=3
GO
--Now set the idenetity_insert OFF to back to the previous track
SET IDENTITY_INSERT YourTable OFF
If got your question right you want to do something like
update table
set identity_column_name = some value
Let me tell you, it is not an easy process and it is not advisable to use it, as there may be some foreign key associated on it.
But here are steps to do it, Please take a back-up of table
Step 1- Select design view of the table
Step 2- Turn off the identity column
Now you can use the update query.
Now redo the step 1 and step 2 and Turn on the identity column
Reference
You need to
set identity_insert YourTable ON
Then delete your row and reinsert it with different identity.
Once you have done the insert don't forget to turn identity_insert off
set identity_insert YourTable OFF
--before running this make sure Foreign key constraints have been removed that reference the ID.
--set table to allow identity to be inserted
SET IDENTITY_INSERT yourTable ON;
GO
--insert everything into a temp table
SELECT *
INTO #tmpYourTable
FROM yourTable
--clear your table
DELETE FROM yourTable
--insert back all the values with the updated ID column
INSERT INTO yourTable (IDCol, OtherCols)
SELECT ID+1 as updatedID --put any other update logic to the ID here
, OtherCols FROM #tmpYourTable
--drop the temp table
DROP TABLE #tmpYourTable
--put identity back to normal
SET IDENTITY_INSERT yourTable OFF;
GO
Try using DBCC CHECKIDENT:
DBCC CHECKIDENT ('YourTable', RESEED, 1);
SET IDENTITY_INSERT dbo.TableName ON
INSERT INTO dbo.TableName
(
TableId, ColumnName1, ColumnName2, ColumnName3
)
VALUES
(
TableId_Value, ColumnName1_Value, ColumnName2_Value, ColumnName3_Value
)
SET IDENTITY_INSERT dbo.TableName OFF
When using Identity_Insert don't forget to include the column names because sql will not allow you to insert without specifying them
DBCC CHECKIDENT(table_name, RESEED, value)
table_name = give the table you want to reset value
value=initial value to be zero,to start identity column with 1
copy your table to a new table without identity column.
select columns into newtable from yourtable
add an identity column to newtable with new seed and make it as a primary key
ALTER TABLE tableName ADD id MEDIUMINT NOT NULL AUTO_INCREMENT KEY
You can also use SET IDENTITY INSERT to allow you to insert values into an identity column.
Example:
SET IDENTITY_INSERT dbo.Tool ON
GO
And then you can insert into an identity column the values you need.
I had similar problem I needed update some IDs what I did was ( i needed to Increase them by 10k ):
set identity_insert YourTable ON
INSERT INTO YourTable
([ID]
,[something1]
,[something2]
,[something3])
SELECT
([ID] + 10000)
,[something1]
,[something2]
,[something3])
FROM YourTable
WHERE something1 = 'needs updeted id'
AND something2 = 'some other condition'
set identity_insert YourTable OFF
DELETE FROM YourTable
WHERE ID >= 'your old ID From'
AND ID <= 'Your old ID To'
And that's it. Hope you understand this logic, in my case there was also PK-FK keys connection with other tables, which meant i had to update them, before i could delete from 'YourTable' original rows.
I know there is already answers to this I just wanted to leave SQL query as example,
ALTER TABLE tablename add newcolumn int
update tablename set newcolumn=existingcolumnname
ALTER TABLE tablename DROP COLUMN existingcolumnname;
EXEC sp_RENAME 'tablename.oldcolumn' , 'newcolumnname', 'COLUMN'
update tablename set newcolumnname=value where condition
However above code works only if there is no primary-foreign key relation
Complete solution for C# programmers using command builder
First of all, you have to know this facts:
In any case, you cannot modify an identity column, so you have to delete the row and re-add with new identity.
You cannot remove the identity property from the column (you would have to remove to column)
The custom command builder from .net always skips the identity column, so you cannot use it for this purpose.
So, once knowing that, what you have to do is. Either program your own SQL Insert statement, or program you own insert command builder. Or use this one that I'be programmed for you. Given a DataTable, generates the SQL Insert script:
public static string BuildInsertSQLText ( DataTable table )
{
StringBuilder sql = new StringBuilder(1000,5000000);
StringBuilder values = new StringBuilder ( "VALUES (" );
bool bFirst = true;
bool bIdentity = false;
string identityType = null;
foreach(DataRow myRow in table.Rows)
{
sql.Append( "\r\nINSERT INTO " + table.TableName + " (" );
foreach ( DataColumn column in table.Columns )
{
if ( column.AutoIncrement )
{
bIdentity = true;
switch ( column.DataType.Name )
{
case "Int16":
identityType = "smallint";
break;
case "SByte":
identityType = "tinyint";
break;
case "Int64":
identityType = "bigint";
break;
case "Decimal":
identityType = "decimal";
break;
default:
identityType = "int";
break;
}
}
else
{
if ( bFirst )
bFirst = false;
else
{
sql.Append ( ", " );
values.Append ( ", " );
}
sql.Append ("[");
sql.Append ( column.ColumnName );
sql.Append ("]");
//values.Append (myRow[column.ColumnName].ToString() );
if (myRow[column.ColumnName].ToString() == "True")
values.Append("1");
else if (myRow[column.ColumnName].ToString() == "False")
values.Append("0");
else if(myRow[column.ColumnName] == System.DBNull.Value)
values.Append ("NULL");
else if(column.DataType.ToString().Equals("System.String"))
{
values.Append("'"+myRow[column.ColumnName].ToString()+"'");
}
else
values.Append (myRow[column.ColumnName].ToString());
//values.Append (column.DataType.ToString() );
}
}
sql.Append ( ") " );
sql.Append ( values.ToString () );
sql.Append ( ")" );
if ( bIdentity )
{
sql.Append ( "; SELECT CAST(scope_identity() AS " );
sql.Append ( identityType );
sql.Append ( ")" );
}
bFirst = true;
sql.Append(";");
values = new StringBuilder ( "VALUES (" );
} //fin foreach
return sql.ToString ();
}
There's a few ways to do this as I've seen., but the best and faster way in my opinion is the following one:
The identity columns have a counter that isn't necessarily the same as the columns registered, you can see the value of this counter with the folowing SQL command:
DBCC CHECKIDENT('tableName', NORESEED);
Then, if you want to edit the identity column you will not be able, but I recommend to make a new register after reseeding the counter to the number you need. To reseed the counter use this command:
DBCC CHECKIDENT('tableName', RESEED, desiredNumber);
I have solved this problem firstly using DBCC and then using insert. For example if your table is
Firstly set new current ID Value on the table as NEW_RESEED_VALUE
MyTable {
IDCol,
colA,
colB
}
DBCC CHECKIDENT('MyTable', RESEED, NEW_RESEED_VALUE)
then you can use
insert into MyTable (colA, ColB) select colA, colB from MyTable
This would duplicate all your records but using new IDCol value starting as NEW_RESEED_VALUE. You can then remove higher ID Value duplicate rows once your have removed/moved their foreign key references, if any.
You can create a new table using the following code.
SELECT IDENTITY (int, 1, 1) AS id, column1, column2
INTO dbo.NewTable
FROM dbo.OldTable
Then delete the old db, and rename the new db to the old db's name. Note: that column1 and column2 represent all the columns in your old table that you want to keep in your new table.
I did the following:
MOVE related data into temporary storage
UPDATE primary key/identity column value (dropping and creating constraints)
RE-INSERT related data with new foreign key value
I wrapped my solution in a STORED PROCEDURE:
CREATE PROCEDURE [dbo].[UpdateCustomerLocationId]
#oldCustomerLocationId INT,
#newCustomerLocationId INT
AS
/*
Updates CustomerLocation.CustomerLocationId #oldCustomerLocationId to #newCustomerLocationId
Example:
EXEC [dbo].[UpdateCustomerLocationId]
#oldCustomerLocationId = 6154874,
#newCustomerLocationId = 50334;
*/
BEGIN
SET NOCOUNT ON;
-- exit if #oldCustomerLocationId does not exists
IF NOT EXISTS (SELECT * FROM dbo.CustomerLocation cl WHERE cl.CustomerLocationId = #oldCustomerLocationId)
BEGIN
PRINT CONCAT('CustomerLocationId ''', #oldCustomerLocationId, ''' (#oldCustomerLocationId) does not exist in dbo.CustomerLocation');
RETURN 1; -- 0 = success, > 0 = failure
END
-- exit if #newCustomerLocationId already exists
IF EXISTS (SELECT * FROM dbo.CustomerLocation cl WHERE cl.CustomerLocationId = #newCustomerLocationId)
BEGIN
PRINT CONCAT('CustomerLocationId ''', #newCustomerLocationId, ''' (#newCustomerLocationId) already exists in dbo.CustomerLocation');
RETURN 2; -- 0 = success, > 0 = failure
END
BEGIN TRAN;
BEGIN -- MOVE related data into temporary storage
IF EXISTS (SELECT * FROM dbo.CustomerLocationData t WHERE t.CustomerLocationId = #oldCustomerLocationId) BEGIN
IF OBJECT_ID('tempdb..#CustomerLocationData') IS NOT NULL
DROP TABLE #CustomerLocationData;
SELECT * INTO #CustomerLocationData FROM dbo.CustomerLocationData t WHERE t.CustomerLocationId = #oldCustomerLocationId;
DELETE t FROM dbo.CustomerLocationData t WHERE t.CustomerLocationId = #oldCustomerLocationId;
END
END
BEGIN -- UPDATE dbo.CustomerLocation
-- DROP CONSTRAINTs
ALTER TABLE [dbo].[CustomerLocation] DROP CONSTRAINT [UC_CustomerLocation];
-- INSERT OLD record with new CustomerLocationId
SET IDENTITY_INSERT dbo.CustomerLocation ON;
INSERT INTO dbo.CustomerLocation
(
CustomerLocationId, CustomerId, LocationId, CustomerLocationIdent, CustomerLocationIdent2, LocationIdent, LocationName, CustomerDistrictId,
CustomerLocationGUID, UpdatedOn, IssueManager, EnrollSelfMonitoring, TemperatureControlDeadlineHour, CreatedOn, OperationBegin, ActiveCustomer,
Comments, LocationName2, ParentGroup, TempString1, TempString2, TempString3, TempString4, TempString5, AutoInheritFromLocation, ClassificationPrimary
)
SELECT #newCustomerLocationId AS CustomerLocationId, CustomerId,LocationId, CustomerLocationIdent, CustomerLocationIdent2, LocationIdent, LocationName, CustomerDistrictId,
CustomerLocationGUID, UpdatedOn, IssueManager, EnrollSelfMonitoring, TemperatureControlDeadlineHour, CreatedOn, OperationBegin, ActiveCustomer,
Comments,LocationName2, ParentGroup, TempString1, TempString2, TempString3, TempString4, TempString5, AutoInheritFromLocation, ClassificationPrimary
FROM dbo.CustomerLocation
WHERE CustomerLocationId = #oldCustomerLocationId;
SET IDENTITY_INSERT dbo.CustomerLocation OFF;
-- DELETE OLD record
DELETE cl FROM dbo.CustomerLocation cl WHERE cl.CustomerLocationId = #oldCustomerLocationId;
-- ADD CONSTRAINTS
ALTER TABLE [dbo].[CustomerLocation] ADD CONSTRAINT [UC_CustomerLocation] UNIQUE NONCLUSTERED ([CustomerId], [LocationId]);
END
BEGIN -- re-INSERT related data from temporary storage
IF OBJECT_ID('tempdb..#CustomerLocationData') IS NOT NULL BEGIN
SET IDENTITY_INSERT dbo.CustomerLocationData ON;
INSERT INTO dbo.CustomerLocationData (Guid, CustomerLocationId, CustomerLocationDataTypeId, Date, Category, Data)
SELECT Guid, #newCustomerLocationId CustomerLocationId, CustomerLocationDataTypeId, Date, Category, Data FROM #CustomerLocationData;
SET IDENTITY_INSERT dbo.CustomerLocationData OFF;
END
END
COMMIT TRAN;
END
This happened to me because I did a merge that was updating the ID that I was doing the Merge on.
Example that did not work (note ClownID):
MERGE ArchitectMain.dbo.BackendClowns AS TGT
USING (
SELECT ClownID
,ClownName
,Description
,Active
,EmailSubject
,AddedBy
,Added
FROM #temptable1
) AS SRC(ClownID, ClownName, Description, Active, EmailSubject, AddedBy, Added)
ON (TGT.ClownID = SRC.ClownID)
WHEN MATCHED
THEN
UPDATE
SET ClownID = SRC.ClownID
,ClownName = SRC.ClownName
,Description = SRC.Description
,Active = SRC.Active
,EmailSubject = SRC.EmailSubject
,AddedBy = SRC.AddedBy
,Added = SRC.Added;
Example that worked (note ClownID):
MERGE ArchitectMain.dbo.BackendClowns AS TGT
USING (
SELECT ClownID
,ClownName
,Description
,Active
,EmailSubject
,AddedBy
,Added
FROM #temptable1
) AS SRC(ClownID, ClownName, Description, Active, EmailSubject, AddedBy, Added)
ON (TGT.ClownID = SRC.ClownID)
WHEN MATCHED
THEN
UPDATE
SET ClownName = SRC.ClownName
,Description = SRC.Description
,Active = SRC.Active
,EmailSubject = SRC.EmailSubject
,AddedBy = SRC.AddedBy
,Added = SRC.Added;
Update is not allowed:
but you can
INSERT new data with correct key
Delete reg
import: all fields must be declared in insert into
sample: reg 5 must be changed to 4:
set IDENTITY_INSERT Gastos_ReclamacionCausa on
insert into Gastos_ReclamacionCausa
(IDCausa,TextoCombo,Asunto,Mensaje,EsBaja)
select 4,TextoCombo,Asunto,Mensaje,EsBaja from Gastos_ReclamacionCausa where idcausa=5
delete from Gastos_ReclamacionCausa where idcausa = 5
set IDENTITY_INSERT Gastos_ReclamacionCausa off
If you specifically need to change the primary key value to a different number (ex 123 -> 1123). The identity property blocks changing a PK value. Set Identity_insert isn't going to work. Doing an Insert/Delete is not advisable if you have cascading deletes (unless you turn off referential integrity checking).
EDIT: Newer versions of SQL don't allow changing the syscolumns entity, so part of my solution has to be done the hard way. Refer to this SO on how to remove Identity from a primary key instead:
Remove Identity from a column in a table
This script will turn off identity on a PK:
***********************
sp_configure 'allow update', 1
go
reconfigure with override
go
update syscolumns set colstat = 0 --turn off bit 1 which indicates identity column
where id = object_id('table_name') and name = 'column_name'
go
exec sp_configure 'allow update', 0
go
reconfigure with override
go
***********************
Next, you can set the relationships so they'll update the foreign key references. Or else you need to turn off relationship enforcement. This SO link shows how:
How can foreign key constraints be temporarily disabled using T-SQL?
Now, you can do your updates. I wrote a short script to write all my update SQL based on the same column name (in my case, I needed to increase the CaseID by 1,000,000:
select
'update ['+c.table_name+'] SET ['+Column_Name+']=['+Column_Name+']+1000000'
from Information_Schema.Columns as c
JOIN Information_Schema.Tables as t ON t.table_Name=c.table_name and t.Table_Schema=c.table_schema and t.table_type='BASE TABLE'
where Column_Name like 'CaseID' order by Ordinal_position
Lastly, re-enable referential integrity and then re-enable the Identity column on the primary key.
Note: I see some folks on these questions ask WHY. In my case, I have to merge data from a second production instance into a master DB so I can shut down the second instance. I just need all the PK/FKs of operations data to not collide. Meta-data FKs are identical.

how to get username into sql trigger when multiple users signed on from asp membership

I am trying to log all the changes to database but was unable to find a way to get current username to the trigger. I have triggerData table which stores the information of user
(guid (userid), data (username), logintime) these are inserted when user sign in.
Here is the trigger
declare #UserIsOnlineTimeWindow DateTime
declare #currenttime DateTime
set #currenttime = GETDATE()
set #UserIsOnlineTimeWindow = 10
DECLARE #uname nvarchar(max)
**set #uname = (SELECT T.UserName from (SELECT distinct u.UserName FROM aspnet_Users u
WHERE IsAnonymous = 'FALSE' AND ((#currenttime - #UserIsOnlineTimeWindow) < u.LastActivityDate)) as t,
TriggerData td, aspnet_Users au
WHERE t.UserName = td.Data and td.guid = au.UserId and td.logintime = (select MAX(td.logintime) from TriggerData td))**
DECLARE #ComputerName nvarchar(50)
DECLARE #IPAddr nvarchar(50)
DECLARE #BroadcastIP nvarchar(50)
DECLARE #auditInsert nvarchar(255)
SET #ComputerName = (SELECT ComputerName FROM inserted)
SET #IPAddr = (SELECT ISNULL(IPAddr,0) FROM inserted)
SET #BroadcastIP = (SELECT ISNULL(BroadcastIP,0) FROM inserted)
SET #auditInsert = #ComputerName+' '+#IPAddr+' '+#BroadcastIP
Begin
INSERT INTO Audit(OldInfo ,NewInfo,[User],Date1,Type,TableName) VALUES('New Record',#auditInsert, #uname ,GETDATE(),'Added','LabIP')
End
The query for the username does not give the currently modifying user instead it gives the recently logged in user. Any help is appreciated. Thanks!
Like marc_s said triggers can and will be applied once per batch. In your case however it sounds like your current application is only inserting once per batch so it's working fine. Your will have a problem however if anyone tries to insert more than one row. Say from another part of the application, or from a query window. It's really up to you if you want to fix it now or wait for it to break (if ever).
I've re-formated the query that is giving you problems.
set #uname = (SELECT T.UserName FROM
(SELECT DISTINCT u.UserName
FROM aspnet_Users u
WHERE IsAnonymous = 'FALSE'
AND ((#currenttime - #UserIsOnlineTimeWindow) < u.LastActivityDate)) AS t
JOIN TriggerData td
ON t.UserName = td.Data
JOIN aspnet_Users au
ON td.guid = au.UserId
WHERE td.logintime = (select MAX(td.logintime) from TriggerData td))
You can see from this where your problem is. If you look in the WHERE clause you will see that you are specifically pulling the row that last logged in. If you are not using an application id (although it sounds like you are) you can use USER_NAME() or SUSER_SNAME() to pull the current user. Unfortunatly based on the information you have provided I don't think you will be able to pull the user that actually made the change, again assuming that you are using an application id.
You might try putting the SPID (id for the current connection) into your users table. You can retrieve this as ##SPID. That way while you are in the current connection you will always be able to tell who the current user is. Of course this will only work if you are holding onto the connection for the whole time the user is logged in (probably not in a web based application).
Last (and unfortunatly least) is rather than using triggers create stored procedures to do your inserts. Pass in the current user into the SP as part of the parameter list and do your logging there.
Sorry I couldn't help more.

PL/SQL - comma separated list within IN CLAUSE

I am having trouble getting a block of pl/sql code to work. In the top of my procedure I get some data from my oracle apex application on what checkboxes are checked. Because the report that contains the checkboxes is generated dynamically I have to loop through the
APEX_APPLICATION.G_F01
list and generate a comma separated string which looks like this
v_list VARCHAR2(255) := (1,3,5,9,10);
I want to then query on that list later and place the v_list on an IN clause like so
SELECT * FROM users
WHERE user_id IN (v_list);
This of course throws an error. My question is what can I convert the v_list to in order to be able to insert it into a IN clause in a query within a pl/sql procedure?
If users is small and user_id doesn't contain commas, you could use:
SELECT * FROM users WHERE ',' || v_list || ',' LIKE '%,'||user_id||',%'
This query is not optimal though because it can't use indexes on user_id.
I advise you to use a pipelined function that returns a table of NUMBER that you can query directly. For example:
CREATE TYPE tab_number IS TABLE OF NUMBER;
/
CREATE OR REPLACE FUNCTION string_to_table_num(p VARCHAR2)
RETURN tab_number
PIPELINED IS
BEGIN
FOR cc IN (SELECT rtrim(regexp_substr(str, '[^,]*,', 1, level), ',') res
FROM (SELECT p || ',' str FROM dual)
CONNECT BY level <= length(str)
- length(replace(str, ',', ''))) LOOP
PIPE ROW(cc.res);
END LOOP;
END;
/
You would then be able to build queries such as:
SELECT *
FROM users
WHERE user_id IN (SELECT *
FROM TABLE(string_to_table_num('1,2,3,4,5'));
You can use XMLTABLE as follows
SELECT * FROM users
WHERE user_id IN (SELECT to_number(column_value) FROM XMLTABLE(v_list));
I have tried to find a solution for that too but never succeeded. You can build the query as a string and then run EXECUTE IMMEDIATE, see http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/dynamic.htm#i14500.
That said, it just occurred to me that the argument of an IN clause can be a sub-select:
SELECT * FROM users
WHERE user_id IN (SELECT something FROM somewhere)
so, is it possible to expose the checkbox values as a stored function? Then you might be able to do something like
SELECT * FROM users
WHERE user_id IN (SELECT my_package.checkbox_func FROM dual)
Personally, i like this approach:
with t as (select 'a,b,c,d,e' str from dual)
--
select val
from t, xmltable('/root/e/text()'
passing xmltype('<root><e>' || replace(t.str,',','</e><e>')|| '</e></root>')
columns val varchar2(10) path '/'
)
Which can be found among other examples in Thread: Split Comma Delimited String Oracle
If you feel like swamping in even more options, visit the OTN plsql forums.

The Link Between Webform Combobox Data and the Database (SQL Server & ASP.NET)

The title, while long, pretty much says it all.
What I have is a master table with a bunch of supporting table relations through foreign keys. For a few of the foreign tables, upon attempting to insert a record into the master table where one of the foreign keys doesn't exist, the data would be passed to the foreign table to create the record first, thereby making the constraint valid and passing the key to the created record back to the insert procedure of the master table.
This data comes from a form in String form, but naturally the foreign key will be an int. The process would look something like this:
-- ASP.NET Web Form --
Requestor Name: _____________ (combobox)
Request: _____________ (dropdownlist)
Date: _____________ (datepicker)
This is a super simplified version, but assume we have a master table with the above data, where both names are foreign keys to a People table. The name fields are comboboxes with a populated list of names linking to People. However, if I wanted to enter a person who didn't yet exist in the People table, the procedure should first create the Person, then use the ID from that new record as the foreign key in the Master table containing columns for the above.
I'm using SQL Server and ASP.NET with VB.NET codebehind. I've been scratching my head over this one for awhile, how to pass data (in different forms such as a foreign key or string) between the web server and DB server, as well as where to validate / transform the data.
It seems the entered name will be passed as an ID if the foreign key exists, and a String if not.
This is my most perplexing problem so far, and no idea where else to look. I've read up on Scott Mitchell's site and others.
MY SOLUTION (?)
The best I can come up with is to pass the user input from the user as a string and convert it to int in the T-SQL procedure. If the value was selected from the drop down, it should match precisely with a valid foreign key. If it doesn't match, then create a new Person and return a foreign key. Is this best practice?
This seems complicated because it is. You have to get your hands dirty. If you need a relational database with ACID support, there's no auto-magical way of getting around it.
Relational databases 101: The primary key must exist before the foreign key can be populated (This is the reason why data warehouse developers populate the dimension table before the fact table). You'll have to design the logic to validate that the primary key exists, insert and get the key if not, and just get the key if exists.
Here's my implementation. I don't know if it's the best, but it worked well for me. Basically I take the values from the controls; in the case of the combobox I need the values from both the TextBox and DropDownList. I then pass those values to the following function in my codebehind:
'This method determines if the name selected already exists in the selection
' options and if so assigns the corresponding ID value to an object variable,
' if not it assigns the value of the `TextBox` to the variable.
Protected Function _ValidateValues(ByRef ddl As DropDownList, ByRef cb As TextBox) As Object
'Ensures the selected value is valid by checking against the entered value in the textbox
If Not String.IsNullOrEmpty(cb.Text) Then
If ddl.Items.Count > 0 Then
If StrComp(cb.Text, ddl.SelectedItem.ToString) = 0 Then
Return ddl.Items.Item(ddl.SelectedIndex).Value 'Returns the index of dropdown selected name
End If
End If
'This counts the capital letters in the entered value and if fewer than 2
' auto capitalizes the first letters. This also allows for project code
' names such as "DOORS" and people names such as "Allen McPherson" etc.
' Be careful though because if "allen McPherson" is entered, it will NOT
' be corrected, though it displays correctly.
Dim rg As New Regex("[A-Z]")
Dim mc As MatchCollection = rg.Matches(cb.Text)
If mc.Count < 2 Then
Return StrConv(cb.Text, VbStrConv.ProperCase)
Else : Return cb.Text
End If
End If
'Returns a SQL DB NULL object if an empty string is submitted
Return DBNull.Value
End Function
Then my stored procedure handles the values something like so...
(Forgive me if I neglected to replace some of the values. I tried to catch them all.)
CREATE PROCEDURE spInsertUser
#User nvarchar(50) = NULL,
#Role nvarchar(50) = NULL,
#RecordID int output -- Returned Value
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- CHECK IF USER EXISTS
-- Insert new record to Users table if the requested user doesn't exist
-- Needed to ensure that the foreign keys are relevant
IF #User = '' OR #User IS NULL BEGIN SET #User = NULL SET #RecordID = NULL END --Ensures that an empty string cannot be submitted, thereby causing an error.
ELSE BEGIN
declare #forename varchar(50), #surname varchar(50)
declare #uid table (ID int)
declare #users table (ID smallint, Name nvarchar(50))
insert into #users
select ID, Name from Users
--If the value can be converted into an int, we need go no further.
BEGIN TRY SET #RecordID = CONVERT(smallint, #User) END TRY
BEGIN CATCH
BEGIN TRY --Otherwise, attempt to parse the name
Set #User = LTRIM(RTRIM(#User)) --Trim the extra space at the beginning and end. This ensures the following "IF" test will evaluate properly.
IF NOT CHARINDEX(' ', #User) > LEN(#User) AND CHARINDEX(' ', #User) > 0 BEGIN -- Confirm First & Last Name exist
Set #forename = RTRIM(LEFT(#User, CHARINDEX(' ',#User,0)-1))
Set #surname = LTRIM(RIGHT(#User, LEN(#User) - CHARINDEX(' ',#User,0)))
Set #User = #forename + ' ' + #surname --Ensure that there is a valid First & Last name
IF LEN(#forename) > 1 AND LEN(#surname) > 1 BEGIN -- Confirm First & Last Name exist
--First ensure that the User doesn't already exist, and if
-- so use their ID, if not insert the new User.
IF NOT EXISTS (select Name from #users where Name like #User) BEGIN --Check if the user already exists
INSERT INTO Users (Name, Forename, Surname) OUTPUT INSERTED.ID INTO #uid Values (#User, -- If not, insert them
#forename, #surname) --Nicely manicured first, last, and full names
SET #RecordID = CONVERT(smallint, (select MAX(ID) from #uid)) END -- Now set the Role to the ID of the new user
ELSE BEGIN --Otherwise if the user already exists, set the Role to the ID of that user
SET #RecordID = (select ID from #users where Name like #User) END
IF NOT EXISTS (select * from rUsersInRoles where UserID = #RecordID) BEGIN
--Do some string manipulation to increase the chances of matching the role
SET #Role = REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(#Role)), ' ', '%'), '.', '%'), '#', '%') --Trims & replaces spaces & periods with wildcards
INSERT INTO rUsersInRoles (UserID, UserRoleID) VALUES
(#RecordID, (select top 1 ID from rUserRoles where Role like #Role)) END
END
END
END TRY
BEGIN CATCH END CATCH
END CATCH
END
END
This stored procedure deals with the case of User Roles as well. If the more simple case of Users only is needed, simply remove the clauses dealing with the checking and insertion of User Roles. :)

Resources