Mysql Cross Schema Queries Based on Row Data - sql

So I'm working on a web application which our clients want their user data separated from other client's user data. Since our clients can be collaborative with each other, we can't just create a new instance of the application for each client, since we have to make sure user ids match up and are unique between clients.
Basically there's our "MasterApplication" database which has a "User" table which contains a UserId and the name of the database where to find the remaining user data. Each client has their own database user which only has access to the "MasterApplication" and their own client table.
Here's an example of how it looks.
CREATE DATABASE MasterDatabase
CREATE DATABASE Client1
CREATE DATABASE Client2
CREATE TABLE `MasterDatabase`.`Person` (
`PersonId` INT NOT NULL AUTO_INCREMENT,
`DatabaseName` Varchar(20) NOT NULL,
PRIMARY KEY (`PersonId`)
)
CREATE TABLE `Client1`.`Person` (
`PersonId` INT NOT NULL,
`FirstName` Varchar(20) NOT NULL,
PRIMARY KEY (`PersonId`)
)
CREATE TABLE `Client2`.`Person` (
`PersonId` INT NOT NULL,
`FirstName` Varchar(20) NOT NULL,
PRIMARY KEY (`PersonId`)
)
INSERT INTO MasterDatabase.Person VALUES (1, 'Client1');
INSERT INTO MasterDatabase.Person VALUES (2, 'Client2');
INSERT INTO Client1.Person VALUES (1, 'Matt');
INSERT INTO Client2.Person VALUES (2, 'Scott');
So Client1 would have a database user which has access to MasterDatabase and Client1 tables. Client2 would have a user with access to MasterDatabase and Client2.
I'm hoping there's some way that I can do a cross schema query easily using the data from the "DatabaseName" field in MasterDatabase, but I can't figure it out. I know for the most part I can store the client's database name in the application logic and just insert it into the query, but there will be a few spots which I'll need to join all clients into one query.
My attempt was to do
SELECT *, `DatabaseName` INTO #DB FROM MasterDatabase.Person
LEFT JOIN #DB.Person ON MasterDatabase.Person.PersonId = #DB.Person.PersonId
But that didn't work as I was hoping. Is there maybe someway I can do it with a procedure? I'm also open to other ideas of separating out the user data per client too if you have any.

I think you can do it in stored procedure. It will look something like
DELIMITER //
CREATE DEFINER='root'#'localhost' PROCEDURE GetData(IN user_id INT)
BEGIN
DECLARE db_name varchar(100);
SELECT DatabaseName INTO db_name FROM MasterDatabase.Person WHERE PersonId = user_id;
SET #t1 =CONCAT('SELECT * FROM ',db_name,'.Person' );
PREPARE stmt1 FROM #t1;
EXECUTE stmt1;
END//
UPDATE Sorry, I forgot DEALLOCATE PREPARE stmt1; ; it should be after EXECUTE stmt1;

Related

Define relationship to tables as a whole

I need to define a table which basically will contain an Id for a user, and a second column which will list names of tables to which the user has access. I can't think of anyway to define any relationships here in case the original table names change. All the logic will be at the application level. However, I would like to be able to define some sort of constraints. How can I do this? Also, I am open to advice regarding any other way to do this.
I am really confused. Doesn't the grant command do exactly what you want? This assumes that the operations you want are database operations.
If you have a more customized set of operations, then you can keep track of table name changes via DDL triggers.
Here's a detailed code example of how to achieve this using RLS
USE tempdb;
GO
CREATE TABLE dbo.OKTable
(
OKTableID int IDENTITY(1,1) NOT NULL
CONSTRAINT PK_dbo_OKTable PRIMARY KEY,
SecuredInfo varchar(100)
);
GO
INSERT dbo.OKTable (SecuredInfo)
VALUES ('Very'), ('Secret'), ('Stuff');
GO
CREATE TABLE dbo.NotOKTable
(
NotOKTableID int IDENTITY(1,1) NOT NULL
CONSTRAINT PK_dbo_NotOKTable PRIMARY KEY,
SecuredInfo varchar(100)
);
GO
INSERT dbo.NotOKTable (SecuredInfo)
VALUES ('Other'), ('Important'), ('Things');
GO
CREATE SCHEMA [Security] AUTHORIZATION dbo;
GO
CREATE TABLE [Security].PermittedTableUsers
(
PermittedTableUsers int IDENTITY(1,1) NOT NULL
CONSTRAINT PK_Security_PermittedTableUsers
PRIMARY KEY,
UserName sysname,
SchemaName sysname,
TableName sysname
);
GO
INSERT [Security].PermittedTableUsers (UserName, SchemaName, TableName)
VALUES (N'dbo', N'dbo', 'OKTable');
GO
ALTER FUNCTION [Security].CheckUserAccess
(
#SchemaName AS sysname,
#TableName AS sysname
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS CheckUserAccessOutcome
WHERE EXISTS (SELECT 1 FROM [Security].PermittedTableUsers AS ptu
WHERE ptu.UserName = USER_NAME()
AND ptu.SchemaName = #SchemaName
AND ptu.TableName = #TableName);
GO
CREATE SECURITY POLICY OKTableAccessFilter
ADD FILTER PREDICATE [Security].CheckUserAccess (N'dbo', N'OKTable')
ON dbo.OKTable
WITH (STATE = ON);
GO
CREATE SECURITY POLICY NotOKTableAccessFilter
ADD FILTER PREDICATE [Security].CheckUserAccess (N'dbo', N'NotOKTable')
ON dbo.NotOKTable
WITH (STATE = ON);
GO
SELECT * FROM dbo.OKTable;
SELECT * FROM dbo.NotOKTable;
GO
It's described more fully in this link:
https://blog.greglow.com/2019/10/10/sql-how-to-control-access-to-sql-server-tables-by-entries-in-another-table/
Since users are NOT SQL Server Logins, therefore, I guess you can use the DDL trigger to monitor table rename where you can change the table name in your custom security table. But I don't know if you can throw exception within this trigger to prevent table rename (simulating some type of constraint). Also it would be better if you store the table name in each line rather saving comma separated table names in 1 field.
If you can utilize SQL Logins then the Gordan's solution is also applicable, but sometimes you cannot create SQL Logins, in-case if you have different application databases along with hundreds of thousands of users.

Multiple table Set-based insert with identity column in table1 and foreign key in table2 [duplicate]

This question already has an answer here:
T-SQL - Insert Data into Parent and Child Tables
(1 answer)
Closed 3 years ago.
I am writing an Application that imports configuration data from multiple external systems. To decouple the user-data from imported data y am using an intermediate table. The following tables are simplified for the sake of the argument.
table [connection] (generic connection information referenced by other tables not included)
table [importConfig] (Connection configuration from one of the external systems)
table [connectionImportConfig] (intermediate table, linking [connection] and [importConfig]
For each connection in [importConfig] I want to create a row in [connection] with an identity column. Then I want to insert the new IDs from [connection] together with the identifier from [importConfig] in [connectionImportConfig].
Constraint: I don't want to use a cursor. Must be a set-based solution.
The examples I have found on stackoverflow are either not set-based or I found them not applicable for other reasons.
T-SQL Insert into multiple linked tables using a condition and without using a cursor (not valid for multiple rows, using ##IDENTITY)
How to perform INSERT/UPDATE to Linking (Join) table which has FKs to IDENTITY PKs (not valid for multiple rows. Not telling how to join the identity values with the original data I want to insert)
I have tried a lot and am stuck at the point where I have to insert the new IDs into connectionImportConfig
-- Test tables&data (stripped down version)
CREATE TABLE connection (ConnectionID int identity(1,1) NOT NULL, comment nvarchar(max) null)
CREATE TABLE connectionImportConfig(connectionID int NOT NULL, ConfigCode nvarchar(50) NOT NULL)
CREATE TABLE importConfig (ConfigCode nvarchar(50) NOT NULL)
DECLARE #MyConnection table (ConnectionID int not null);
insert into importConfig values ('a')
insert into importConfig values ('b')
insert into importConfig values ('c')
-- Insert into PK-table creating the IDs
INSERT INTO connection (comment)
OUTPUT INSERTED.ConnectionID INTO #MyConnection
SELECT * from importConfig
-- How do I insert the new IDs together with the ConfigCode? 1 has to be replaced with the ID.
-- JOIN doesn't seem to work because there is no join condition to use
INSERT INTO connectionImportConfig (connectionID, ConfigCode)
SELECT 1, ConfigCode FROM ImportConfig
select * from #MyConnection
select * from connectionImportConfig
-- Cleanup
DROP TABLE importConfig;
DROP TABLE connection;
DROP table connectionImportConfig;
Have a look at this. I am sure you could loosely modify this to your needs. I don't typically like inserting right off the output but it really depends on your data. Hope this example helps.
IF OBJECT_ID('tempdb..#ImportConfig') IS NOT NULL DROP TABLE #ImportConfig
IF OBJECT_ID('tempdb..#Config') IS NOT NULL DROP TABLE #Config
IF OBJECT_ID('tempdb..#Connection') IS NOT NULL DROP TABLE #Connection
GO
CREATE TABLE #ImportConfig (ImportConfigID INT PRIMARY KEY IDENTITY(1000,1), ImportConfigMeta VARCHAR(25))
CREATE TABLE #Config (ConfigID INT PRIMARY KEY IDENTITY(2000,1), ImportConfigID INT, ConfigMeta VARCHAR(25))
CREATE TABLE #Connection (ConnectionID INT PRIMARY KEY IDENTITY(3000,1), ConfigID INT, ConnectionString VARCHAR(50))
INSERT INTO #ImportConfig (ImportConfigMeta) VALUES
('IMPORT_ConfigMeta1'),('IMPORT_ConfigMeta2')
;MERGE
INTO #Config AS T
USING #ImportConfig AS S
ON T.ConfigID = S.ImportConfigID
WHEN NOT MATCHED THEN
INSERT (ImportConfigID, ConfigMeta) VALUES (
S.ImportConfigID,
REPLACE(S.ImportConfigMeta,'IMPORT_','')
)
OUTPUT INSERTED.ConfigID, 'CONNECTION_STRING: ' + INSERTED.ConfigMeta INTO #Connection;
SELECT 'IMPORT CONFIG' AS TableName, * FROM #ImportConfig
SELECT 'CONFIG' AS TableName, * FROM #Config
SELECT 'CONNECTION' AS TableName, * FROM #Connection

Stop double entry of data in database

I am using VS 2008 and SQL Server 2005. And the problem is that when I insert a new record which is string data. It continues on entering the same data which is already exiting in the table, again and again. But I want that where my insert query is running. I place the check there that it does not allow similar data in the table.
My scenario:
I have to decide on these two string columns: 'source' and 'destination'
If similar source and destination occur in any record we must stop we the entry on record.
Share the solution.
The easiest way to do it is by putting a 'UNIQUE constraint' on your database. Then, each time an SQL UPDATE or an SQL INSERT is executed, the database server would check the validity of the new SQL action and cancel it if it violates your data integrity constraing.
For example (copying from this SQL tutorial):
CREATE TABLE Persons
(
P_Id int NOT NULL UNIQUE,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
If you want to add a UNIQUE constraint on two columns, you could use such a statement:
CREATE TABLE Example
(Col1 int NOT NULL,
Col2 int NOT NULL,
UNIQUE (Col1, Col2)
)
Hope I helped!

SQL Server 2008 stored procedure for a table having two foreign keys and both of them is a composite key

I have the following tables :
create table ApartmentInfo(
ApartmentId int primary key identity,
ApName nvarchar(50))
create table [User](
UserId int primary key identity,
FirstName nvarchar(50),
LastName nvarchar(50),
Username nvarchar(50),
[Password] nvarchar(50),
[Description] nvarchar(200))
create table ApUser(
ApartmentId int foreign key references ApartmentInfo(ApartmentId),
UserId int foreign key references [User](UserId),
primary key(ApartmentId,UserId))
Summary of the usage is: suppose I have 10 apartments in my Apartmentinfo table and 3 users in the [User] table.
Now I want to write a stored procedure such that:
every UserId has all 10 ApartmentId's, and whenever a new apartment is created in ApartmentInfo table it will also be added in ApUser table again having all 3 userId's.
And if a new user is created in User table then it will also have all the 10 ApartmentId's related with it in ApUser table.
Thanks in advance, I am new to SQL Server and I don't know how it can be done or not but, if it is possible then please let me know, I will be grateful to you, thanks.
If you're not dead set on using a stored procedure this use case seems like an excellent fit for server side triggers:
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[trigger_insert_apartment]'))
DROP TRIGGER [dbo].[trigger_insert_apartment]
GO
CREATE TRIGGER trigger_insert_apartment
ON ApartmentInfo FOR INSERT
AS INSERT ApUser(ApartmentId, UserId ) SELECT i.ApartmentId, u.UserId FROM [User] AS u, inserted AS i
GO
IF EXISTS (SELECT * FROM sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[trigger_insert_user]'))
DROP TRIGGER [dbo].[trigger_insert_user]
GO
CREATE TRIGGER trigger_insert_user
ON [User] FOR INSERT
AS INSERT ApUser(ApartmentId,UserId ) SELECT a.ApartmentId, i.UserId FROM ApartmentInfo AS a, inserted AS i
GO
These two triggers will insert all apartments or users into the apinfo table when you add a new user or apartment, and if I understood your question that was what you wanted?
To address your question about what you'd do to associate apartments with users IF YOU WANTED TO LATER...
If you're restricting which apartments a user has access to view, I would NOT associate them individually to every user. BUT, if they're manually adding a "watch" on an apartment in your application, then this is ok to do. If you want to only show them a certain set of apartments, try to group them in a logical way if you can. So maybe have an ApartmentGroup lookup table:
ApartmentGroup(ApartmentGroupId (Int), ApartmentGroupName varchar(50))
and assign an ApartmentGroupId to each apartment. Then you can have a join table for ApUser and ApartmentGroup. That way you're not associating every apartment with every user.
However, if you want to add the apartment to each user still, then in your InsertApartment stored procedure, just insert it into the ApUser table as well.

Foreign Key is null when insert using Stored Procedure

I've created a insert stored procedure with two tables like in the exapmle:
Table NameAge
CREATE TABLE [dbo].[Assignment3_NameAge]
(
userID int PRIMARY KEY IDENTITY(1,1),
Name varchar(255) NOT NULL,
Age int NOT NULL
)
Table Hobbies
CREATE TABLE [dbo].[Assignment3_Hobbies]
(
hobbiesID int Identity(1,1) Primary Key,
userID int Foreign Key references Assignment3_NameAge(userID),
hobbies varchar(255) NOT NULL,
)
Insert Stored Procedure
CREATE PROCEDURE [dbo].p_Assignment3Join_ins
#Name nvarchar(100),
#Age int,
#Hobbies nvarchar(100)
AS
INSERT INTO [TABLE].[dbo].[Assignment3_NameAge]
([Name]
,[Age])
VALUES (#Name,#Age)
INSERT INTO [TABLE].[dbo].[Assignment3_Hobbies]
([Hobbies])
VALUES (#Hobbies)
The problem is that when i run the stored procedure the table Hobbies has a null value for userid(the foreign key)
What am i doing wrong?
You should provide the key of the Assignment3_NameAge value you want to insert into Assignment3_Hobbies.
If you want the last inserted you can use SCOPE_IDENTITY() from SQL Server(if you're using SQL Server) or equivalent. It will give you the last inserted value from Assignment3_NameAge
I am guessing this is SQL Server based on the IDENTITY column. Correct?
The first insert creates a user, but there is no user ID being set on the insert of the hobby. You need to capture the identity value from the first insert to be used in the second insert. Have you gon over the system functions available?
You're not supplying a value for it, SQL won't automagically fill the value in for you even though you've created a Foreign Key relationship. It's your job to populate the tables.