I need to extract data from a third party system (I have no influence over its design). It's a SQL Server 2005 database uses bitmaps to store user privileges. It has five INT fields giving a maximum of 5 * 32 = 160 privileges. It stores a number of types of privilege and re-uses the bitmaps for each type. So in total there are 6 fields that drive privileges. Each privilege can be assigned to a specific item of a given type.
An example of a type is “table” so items in that context would be table names.
The privilege table looks like this:
ID | PRIVTYPE | USERNAME | ITEMNAME | BITMAP1 | BITMAP2 | BITMAP3 | BITMAP4 | BITMAP5
For example
123 | Table | Joe | Customers | 0x408 | 0x1 | 0x5c | 0x1000 | 0x0
Another table contains the privileges represented by each bit. It looks like this:
PRIVTYPE | BITMAP_ID | BITVALUE | PRIVILEGE_NAME
For example, entries relating to the above bitmaps would be:
Table | 1 |0x8 | View
Table | 1 |0x400 | Edit
Table | 2 |0x1 | Report
Table | 3 |0x4 | View Address Data
Table | 3 |0x8 | View Order Data
Table | 3 |0x10 | View Payment Data
Table | 3 |0x40 | View System Data
Table | 4 |0x1000| View Hidden Fields
I want to somehow parse the privilege table into a new table or view that will have one record per user per item privilege. Like this:
USERNAME | ITEMNAME |PRIVILEGE_NAME
Joe | Table | Customers | View
Joe | Table | Customers | Edit
Joe | Table | Customers | Report
Joe | Table | Customers | view Address Data
Joe | Table | Customers | view Order Data
Joe | Table | Customers | view Payment Data
Joe | Table | Customers | view System Data
Joe | Table | Customers | view Hidden Fields
I think I need to create a view by running a select statement that will return multiple rows for each row in the privilege table: one row for every set bit in a bitmask field. So, for example, a single row in the privilege table that has 3 bits set in the bitmasks will cause three rows to be returned.
I have searched for answers about breaking tables into multiple rows. I’ve looked at various joins and pivots but I can’t find something that will do what I need. Is the above possible? Any guidance appreciated…
You could unpivot the first table (called #UserPrivileges below) and join it to the second one (#Privileges) on privilege type, bitmap ID and the result of bitwise AND between the bitmap in the first table and BITVALUE in the second table.
Below is my implementation.
Setup:
DECLARE #UserPrivileges TABLE (
ID int,
PRIVTYPE varchar(50),
USERNAME varchar(50),
ITEMNAME varchar(50),
BITMAP1 int,
BITMAP2 int,
BITMAP3 int,
BITMAP4 int,
BITMAP5 int
);
INSERT INTO #UserPrivileges
(ID, PRIVTYPE, USERNAME, ITEMNAME, BITMAP1, BITMAP2, BITMAP3, BITMAP4, BITMAP5)
SELECT 123, 'Table', 'Joe', 'Customers', 0x408, 0x1, 0x5c, 0x1000, 0x0
;
DECLARE #Privileges TABLE (
PRIVTYPE varchar(50),
BITMAP_ID int,
BITVALUE int,
PRIVILEGE_NAME varchar(50)
);
INSERT INTO #Privileges (PRIVTYPE, BITMAP_ID, BITVALUE, PRIVILEGE_NAME)
SELECT 'Table', 1, 0x8 , 'View ' UNION ALL
SELECT 'Table', 1, 0x400 , 'Edit ' UNION ALL
SELECT 'Table', 2, 0x1 , 'Report ' UNION ALL
SELECT 'Table', 3, 0x4 , 'View Address Data ' UNION ALL
SELECT 'Table', 3, 0x8 , 'View Order Data ' UNION ALL
SELECT 'Table', 3, 0x10 , 'View Payment Data ' UNION ALL
SELECT 'Table', 3, 0x40 , 'View System Data ' UNION ALL
SELECT 'Table', 4, 0x1000, 'View Hidden Fields'
;
Query:
WITH unpivoted AS (
SELECT
ID,
PRIVTYPE,
USERNAME,
ITEMNAME,
RIGHT(BITMAP_ID, 1) AS BITMAP_ID, -- OR: STUFF(BITMAP_ID, 1, 6, '')
-- OR: SUBSTRING(BITMAP_ID, 7, 999)
-- OR: REPLACE(BITMAP_ID, 'BITMAP', '')
BITMAP_VAL
FROM UserPrivileges
UNPIVOT (
BITMAP_VAL FOR BITMAP_ID IN (
BITMAP1, BITMAP2, BITMAP3, BITMAP4, BITMAP5
)
) u
),
joined AS (
SELECT
u.USERNAME,
u.PRIVTYPE,
u.ITEMNAME,
p.PRIVILEGE_NAME
FROM unpivoted u
INNER JOIN Privileges p
ON u.PRIVTYPE = p.PRIVTYPE
AND u.BITMAP_ID = p.BITMAP_ID
AND u.BITMAP_VAL & p.BITVALUE <> 0
)
SELECT * FROM joined
Results:
USERNAME PRIVTYPE ITEMNAME PRIVILEGE_NAME
-------- -------- --------- ------------------
Joe Table Customers View
Joe Table Customers Edit
Joe Table Customers Report
Joe Table Customers View Address Data
Joe Table Customers View Order Data
Joe Table Customers View Payment Data
Joe Table Customers View System Data
Joe Table Customers View Hidden Fields
If this is a ONE OFF task or something that will only run RARELY, then CURSORS!
Otherwise, just do 6 distinct select statements that are unionized in your insert:
INSERT INTO FOO (myName, myValue)
SELECT myName, myCol1
From BAR
UNION
SELECT myName, myCol2
FROM BAR
UNION
SELECT myName, myCol3
FROM BAR
Related
I have a target table for which partial data arrives at different times from 2 departments. The keys they use are the same, but the fields they provide are different. Most of the rows they provide have common keys, but there are some rows that are unique to each department. My question is about the fields, not the rows:
Scenario
the target table has a key and 30 fields.
Dept. 1 provides fields 1-20
Dept. 2 provides fields 21-30
Suppose I loaded Q1 data from Dept. 1, and that created new rows 100-199 and populated fields 1-20. Later, I receive Q1 data from Dept. 2. Can I execute the same merge code I previously used for Dept. 1 to update rows 100-199 and populate fields 21-30 without unintentionally changing fields 1-20? Alternatively, would I have to tailor separate merge code for each Dept.?
In other words, does (or can) "Merge / Update" operate only on target fields that are present in the source table while ignoring target fields that are NOT present in the source table? In this way, Dept. 1 fields would NOT be modified when merging Dept. 2, or vice-versa, in the event I get subsequent corrections to this data from either Dept.
You can use a merge instruction, where you define a source and a target data, and what happens when a registry is found on both, just on the source, just on the target, and even expand it with custom logic, like it's just on the source, and it's older than X, or it's from department Y.
-- I'm skipping the fields 2-20 and 22-30, just to make this shorter.
create table #target (
id int primary key,
field1 varchar(100), -- and so on until 20
field21 varchar(100), -- and so on until 30
)
create table #dept1 (
id int primary key,
field1 varchar(100)
)
create table #dept2 (
id int primary key,
field21 varchar(100)
)
/*
Creates some data to merge into the target.
The expected result is:
| id | field1 | field21 |
| - | - | - |
| 1 | dept1: 1 | dept2: 1 |
| 2 | | dept2: 2 |
| 3 | dept1: 3 | |
| 4 | dept1: 4 | dept2: 4 |
| 5 | | dept2: 5 |
*/
insert into #dept1 values
(1,'dept1: 1'),
--(2,'dept1: 2'),
(3,'dept1: 3'),
(4,'dept1: 4')
insert into #dept2 values
(1,'dept2: 1'),
(2,'dept2: 2'),
--(3,'dept2: 3'),
(4,'dept2: 4'),
(5,'dept2: 5')
-- Inserts the data from the first department. This could be also a merge, it necessary.
insert into #target(id, field1)
select id, field1 from #dept1
merge into #target t
using (select id, field21 from #dept2) as source_data(id, field21)
on (source_data.id = t.id)
when matched then update set field21=source_data.field21
when not matched by source and t.field21 is not null then delete -- you can even use merge to remove some records that match your criteria
when not matched by target then insert (id, field21) values (source_data.id, source_data.field21); -- Every merge statement should end with ;
select * from #target
You can see this code running on this DB Fiddle
How can I combine 2 tables into one flattened table (1 row per person) so that the columns retrieved from the first table appear as they were but the values in the second table are combined into a single XML column?
e.g. Table 1
Person_gid
Name
1
Mary
2
Barry
3
Liam
Table 2
Person_gid
Subjects
1
Physics
1
Chemistry
How would I write a query to result in something like this:
Person_gid
Name
Books
1
Mary
<Physics, Chemistry>
2
Barry
3
Liam
I understand that I'll need to do a LEFT JOIN here but it's the XML bit that I need help with please.
Note: The XML won't look exactly like the above but I was having trouble writing XML in the markup that I used for the tables.
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #person TABLE (Person_gid INT IDENTITY PRIMARY KEY, person_name VARCHAR(30));
INSERT INTO #person (person_name) VALUES
('Mary'),
('Barry'),
('Liam');
DECLARE #subject TABLE (Person_gid INT, [subject] VARCHAR(30));
INSERT INTO #subject (Person_gid, [subject]) VALUES
(1, 'Physics'),
(1, 'Chemistry');
-- DDL and sample data population, end
SELECT p.*, (
SELECT subject FROM #subject AS s
WHERE s.Person_gid = p.Person_gid
FOR XML PATH(''), TYPE, ROOT('root')
) AS books
FROM #person AS p;
Output
+------------+-------------+---------------------------------------------------------------------+
| Person_gid | person_name | books |
+------------+-------------+---------------------------------------------------------------------+
| 1 | Mary | <root><subject>Physics</subject><subject>Chemistry</subject></root> |
| 2 | Barry | NULL |
| 3 | Liam | NULL |
+------------+-------------+---------------------------------------------------------------------+
Hi just wondering if this scenario is possible?
I have two tables and a relationship table to create a many to many relationships between the two tables. See the below tables for a simple representation;
| Security ID | Security Group |
| 1 | Admin |
| 2 | Basic |
| Security ID | Access ID |
| 1 | NULL |
| 2 | 1 |
| Function ID | Function Code |
| 1 | Search |
| 2 | Delete |
What I want to achieve is while checking the relationship table I want to return all functions a user on a security group has access to. If the user is assigned to a security group that contains a NULL value in the relationship table then grant them access to all functions.
For instance, a user on the "Basic" security group would have access to the search function while a user on the "Admin" security group should have access to both Search and Delete.
The reason it is set up this way is because a user can have 0 to many security groups and the list of functions is very large requiring the use of a whitelist of functions you can access instead of a list of a blacklist of functions you can't access.
Thank you for your time.
Your tables' sample:
CREATE TABLE #G
(
Security_ID INT,
Security_Group VARCHAR(32)
)
INSERT INTO #G
VALUES (1, 'Admin'), (2, 'Basic')
CREATE TABLE #A
(
Security_ID INT,
Access_ID INT
)
INSERT INTO #A
VALUES (1, NULL), (2, 1)
CREATE TABLE #F
(
Function_ID INT,
Function_CODE VARCHAR(32)
)
INSERT INTO #F
VALUES (1, 'Search'), (2, 'Delete')
Query:
SELECT #G.Security_Group, #F.Function_CODE
FROM #G
JOIN #A ON #G.Security_ID = #A.Security_ID
JOIN #F ON #F.Function_ID = #A.Access_ID OR #A.Access_ID IS NULL
Dropping the sample tables:
DROP TABLE #G
DROP TABLE #A
DROP TABLE #F
I have an immense doubt, is it possible to create a column calculated using two tables?
Table 1:
---------------------
id | Value1 |
---------------------
1 | 25 |
Table 2
---------------------
id | Value2 |
---------------------
1 | 5 |
Now, in a 3rd table I want a calculated column of the values 1 and 2?? is it possible?
Table 3
---------------------
id | Sumvalues |
---------------------
1 | ? |
or is there another method that can be used for "sumvalues" to self-adjust with the change of the other fields related to it?
The best option is to create a view in my opinion:
create view vMyView as
select T1.id,
T1.Value1 + T2.Value2
from [Table1] T1 join [Table2] T2 on T1.id = T2.id
This way, everytime you execute query against the view, you will get most actual data.
Use for adding value1 to value2:
SET #value1 = SELECT value1 FROM TABLE1;
SET #value2 = SELECT value2 FROM TABLE2;
SET #value3 = #value1+#value2;
INSERT INTO TABLE3 (value3) VALUES (#value3);
This may contain typos since I’m writting from a cell phone.
I have following data in employee Table A:
ID | emp | City_Type | City
1 | 101 | Z | Tokyo
2 | 101 | Y | New York
City_Type can either be Y or Z. Y being the city this person was born in, Z is the city he/she is living now.
I need to put these together in a table 'B' which look like the folowing:
ID | emp | Current_City | Birth_City
So in the end, Table B must be filled like this:
ID | emp | Current_City | Birth_City
1 | 101 | Tokyo | New York
(in some cases, one of the 2 can be empty/null)
Any suggestions on how to do this? I haven't been able to found much information on this myself.
I did this exercise (using sql-server) with PIVOT TABLE:
select emp, Z 'Current_City' , Y 'Birth_City' from
(
select emp,City_Type, City from TABLE__A
) x
pivot
(
max(City) FOR City_Type in (Z,Y)
) AS PivotTable
below the result achieved, with an example of a NULL value for the field Current_City
emp Current_City Birth_City
101 Tokyo New York
102 NULL London
I omitted ID, it is not clear from the request if and what needs to be added ( minimum, maximum on emp , or a new calculated or due to the INSERT in TABLE__B)
This previous query can be used to insert into TABLE__B
INSERT INTO [TABLE__B]
([emp]
,[Current_City]
,[Birth_City])
...
First create your TableB and populate [Current_City] and [Birth_City] with nulls, but make sure [emp] is there and it has all the employees you intend to modify.
Then run this SQL modified to fit your database / schema / table names / etc:
update TableB
set Current_City = (select City
from TableA
where TableA.City_Type ='Z'
and TableA.emp = TableB.emp),
Birth_City = (select City
from TableA
where TableA.City_Type ='Y'
and TableA.emp = TableB.emp)
One way would be to use the PIVOT transformation.