英文:
SQL query to find unique occurrences inside cross join
问题
我有三个表:Users(用户)、Rights(权限)和UserRights(用户权限),它们用于映射前两个表的值。示例:
表 'Users'
ID | LOGIN |
---|---|
1 | test1 |
2 | test2 |
表 'Rights'
ID | NAME |
---|---|
1 | read |
2 | write |
3 | write |
表 'UserRights'
USER_ID | RIGHT_ID |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
2 | 2 |
我有一个查询来检查用户是否具有适当的权限。查询的输出如下所示:
LOGIN | RIGHTS |
---|---|
test1 | read=true;write=true |
test2 | read=false;write=true |
问题在于表 'Rights' 中可能存在一些重复项与其他关系。因此,脚本的输出可能如下所示:
LOGIN | RIGHTS |
---|---|
test1 | read=true;write=true;write=true |
test2 | read=false;write=true;write=false |
我希望只选择不同的权限,并且只要其中一个重复项具有“true”值,我就要选择每个这样的权限的“true”值。
我使用以下查询来实现预期的结果,但它不考虑重复项中是否有“true”值的情况:
SELECT u.login,
LISTAGG(DISTINCT r.name || '=' ||
CASE
WHEN ur.user_id IS NOT NULL THEN
'true'
ELSE
'false'
END,
'; ')
WITHIN GROUP ( ORDER BY r.name ) AS rights
FROM users u
CROSS JOIN rights r
LEFT JOIN userrights ur
ON ur.user_id = u.id
AND ur.right_id = r.id
GROUP BY u.login;
此外,我希望不打印 r.name,但我使用它来过滤唯一的值。
我们现在正在从这个模式迁移,所以更难纠正数据库一致性而不是纠正脚本。
英文:
I have three tables: Users, Rights and UserRights, which is mapping values from two first tables. Example:
Table 'Users'
ID | LOGIN |
---|---|
1 | test1 |
2 | test2 |
Table 'Rights'
ID | NAME |
---|---|
1 | read |
2 | write |
3 | write |
Table 'UserRights'
USER_ID | RIGHT_ID |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
2 | 2 |
I have a query to check whether the user has the appropriate right. The output of the query looks like this:
LOGIN | RIGHTS |
---|---|
test1 | read=true;write=true |
test2 | read=false;write=true |
The problem is that inside the table 'Rights' there could be some duplicates with other relationships. Because of them script output can look like this:
LOGIN | RIGHTS |
---|---|
test1 | read=true;write=true;write=true |
test2 | read=false;write=true;write=false |
I'd like to select only distinct rights and I want to select 'true' value for each such right if at least one of duplicates has 'true'.
I use this query to achieve the expecting result but it does not consider if any of duplicates has 'true' value for the right:
SELECT u.login,
LISTAGG(DISTINCT r.name || '=' ||
CASE
WHEN ur.user_id IS NOT NULL THEN
'true'
ELSE
'false'
END,
'; ')
WITHIN GROUP ( ORDER BY r.name ) AS rights
FROM users u
CROSS JOIN rights r
LEFT JOIN userrights ur
ON ur.user_id = u.id
AND ur.right_id = r.id
GROUP BY u.login;
Also, I'd like to get rid of printing r.name, but I use it to filter out unique values.
We are migrating from this schema now, so it is more difficult to correct DB consistency than correct the script.
答案1
得分: 1
以下是翻译好的部分:
修复您的数据(而不是查询),通过向表添加主键/唯一约束来实现:
然后在表中不可能有重复的权限。
无论是否应用约束,都不可能同时具有“true”和“false”权限,因为“true”由“UserRights”表中具有匹配行的存在给出,而“false”由“UserRights”表中不存在匹配行给出,永远不可能出现同一用户和权限组合既存在又不存在“UserRights”表中的行的情况;这些行为是互斥的。
如果在应用约束之前存在重复项,可以使用以下方式去除它们:
然后,删除重复项并应用约束后,可以使用您的查询(或根据主键进行聚合):
如果“UserRights”表中存在重复项,您应该修复数据并添加约束;如果您不打算这样做(您应该这样做),那么可以使用“DISTINCT”:
或:
更新:具有重复名称的权限
如果权限具有重复名称,请修复您的数据。
然后,您可以合并重复项:
然后删除(现在未使用的)重复权限:
然后,您可以添加约束以防止再次发生这种情况:
然后:
英文:
Fix your data (not your query) by adding primary key/unique constraints to your tables:
CREATE TABLE Users (
ID NUMBER(8,0) PRIMARY KEY,
LOGIN VARCHAR2(20) NOT NULL UNIQUE
);
CREATE TABLE Rights (
ID NUMBER(5,0) PRIMARY KEY,
NAME VARCHAR2(20) UNIQUE NOT NULL
);
CREATE TABLE UserRights (
USER_ID REFERENCES Users(id),
RIGHT_ID REFERENCES Rights(id),
PRIMARY KEY (user_id, right_id)
);
INSERT INTO users (id, login)
SELECT 1, 'test1' FROM DUAL UNION ALL
SELECT 2, 'test2' FROM DUAL;
INSERT INTO rights (id, name)
SELECT 1, 'read' FROM DUAL UNION ALL
SELECT 2, 'write' FROM DUAL;
INSERT INTO UserRights (USER_ID, RIGHT_ID)
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL;
Then it is impossible to have duplicate rights in a table.
It is also impossible (with or without the constraints) to have a right be simultaneously true
and false
as true
is given by the existence of a matching row in the UserRights
table and false
is given by the non-existence of a matching row in the UserRights
table and there can never be a case when for the same user and right combination there both exists and does not exist a row in the UserRights
table; the behaviours are mutually exclusive.
If, before you apply the constraints, you have duplicates then you can get rid of them using:
DELETE FROM userrights
WHERE ROWID IN (
SELECT ROWID
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY user_id, right_id ORDER BY ROWID) AS rn
FROM userrights
)
WHERE rn > 1
);
After removing duplicates and applying your constraints, then you can use your query (or aggregating on the primary key):
SELECT MAX(u.login) AS login,
LISTAGG(r.name || '=' || NVL2(ur.user_id, 'true', 'false'), ';')
WITHIN GROUP (ORDER BY r.name) AS rights
FROM users u
CROSS JOIN rights r
LEFT OUTER JOIN userrights ur
ON (u.id = ur.user_id AND r.id = ur.right_id)
GROUP BY u.id
Which, for your sample data, outputs:
LOGIN | RIGHTS |
---|---|
test1 | read=true;write=false |
test2 | read=true;write=true |
If you do have duplicates in the UserRights
table then you should fix the data and add constraints; if you are not going to do that (you should) then you can use DISTINCT
:
SELECT MAX(u.login) AS login,
LISTAGG(r.name || '=' || NVL2(ur.user_id, 'true', 'false'), ';')
WITHIN GROUP (ORDER BY r.name) AS rights
FROM users u
CROSS JOIN rights r
LEFT OUTER JOIN (SELECT DISTINCT * FROM userrights) ur
ON (u.id = ur.user_id AND r.id = ur.right_id)
GROUP BY u.id;
or:
SELECT MAX(u.login) AS login,
LISTAGG(DISTINCT r.name || '=' || NVL2(ur.user_id, 'true', 'false'), ';')
WITHIN GROUP (ORDER BY r.name) AS rights
FROM users u
CROSS JOIN rights r
LEFT OUTER JOIN userrights ur
ON (u.id = ur.user_id AND r.id = ur.right_id)
GROUP BY u.id;
Update: Rights with duplicate names
If you have rights with duplicate names then fix your data.
For example, given the data:
CREATE TABLE Users (
ID NUMBER(8,0) PRIMARY KEY,
LOGIN VARCHAR2(20) NOT NULL UNIQUE
);
CREATE TABLE Rights (
ID NUMBER(5,0) PRIMARY KEY,
NAME VARCHAR2(20) NOT NULL
);
CREATE TABLE UserRights (
USER_ID REFERENCES Users(id),
RIGHT_ID REFERENCES Rights(id),
CONSTRAINT userrights__uid__rid__pk PRIMARY KEY (user_id, right_id)
);
INSERT INTO users (id, login)
SELECT 1, 'test1' FROM DUAL UNION ALL
SELECT 2, 'test2' FROM DUAL UNION ALL
SELECT 3, 'test3' FROM DUAL UNION ALL
SELECT 4, 'test4' FROM DUAL;
INSERT INTO rights (id, name)
SELECT 1, 'read' FROM DUAL UNION ALL
SELECT 2, 'write' FROM DUAL UNION ALL
SELECT 3, 'write' FROM DUAL;
INSERT INTO UserRights (USER_ID, RIGHT_ID)
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 2 FROM DUAL UNION ALL
SELECT 3, 3 FROM DUAL UNION ALL
SELECT 4, 3 FROM DUAL;
Then you can merge the duplicates using:
ALTER TABLE userrights MODIFY CONSTRAINT userrights__uid__rid__pk DISABLE;
UPDATE userrights ur
SET right_id = (
SELECT new_id
FROM (
SELECT id,
MIN(id) OVER (PARTITION BY name) AS new_id
FROM rights
) r
WHERE r.id = ur.right_id
);
DELETE FROM userrights
WHERE ROWID IN (
SELECT ROWID
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY user_id, right_id ORDER BY ROWID) AS rn
FROM userrights
)
WHERE rn > 1
)
ALTER TABLE userrights MODIFY CONSTRAINT userrights__uid__rid__pk ENABLE;
And delete (now unused) the duplicate rights:
DELETE FROM rights
WHERE ROWID IN (
SELECT ROWID
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY name ORDER BY id) AS rn
FROM rights
)
WHERE rn > 1
)
Then you can add a constraint to prevent this happening again:
ALTER TABLE rights ADD CONSTRAINT rights__name__u UNIQUE (name);
Then:
SELECT MAX(u.login) AS login,
LISTAGG(r.name || '=' || NVL2(ur.user_id, 'true', 'false'), ';')
WITHIN GROUP (ORDER BY r.name) AS rights
FROM users u
CROSS JOIN rights r
LEFT OUTER JOIN userrights ur
ON (u.id = ur.user_id AND r.id = ur.right_id)
GROUP BY u.id
Outputs:
LOGIN | RIGHTS |
---|---|
test1 | read=true;write=false |
test2 | read=true;write=true |
test3 | read=false;write=true |
test4 | read=false;write=true |
答案2
得分: 0
以下是已翻译的内容:
有一个执行此操作的选项,可以如下所示:
子查询检查特定用户是否具有读取、写入或两者权限,并将它们放在用户的第一行。主查询只是将这些值连接在一起,并给它们一个true或false,如果它们存在或不存在。
使用您提供的示例数据:
结果如下:
LOGIN RIGHTS
----- -----------------------
test1 Read=true;Write=true;
test2 Read=false;Write=true;
此外,如果您有一个用户不在users_rights表中,比如用户ID=3 LOGIN=test3,那么将返回另一行,如下:
LOGIN RIGHTS
----- -----------------------
test1 Read=true;Write=true;
test2 Read=false;Write=true;
test3 Read=false;Write=false;
英文:
An option to do it could be like here:
SELECT LOGIN,
CASE WHEN READ_RIGHT Is Null THEN 'Read=false;' ELSE 'Read=true;' END ||
CASE WHEN WRITE_RIGHT Is Null THEN 'Write=false;' ELSE 'Write=true;' END "RIGHTS"
FROM
( SELECT u.LOGIN, ROW_NUMBER() OVER(Partition By u.ID Order By u.ID, Nvl(r.ID, 0)) "RN",
CASE WHEN Upper(r.A_NAME) = 'READ' THEN r.A_NAME
ELSE FIRST_VALUE(CASE WHEN Upper(r.A_NAME) = 'READ' THEN r.A_NAME END)
OVER(Partition By u.ID Order By u.ID, Nvl(r.ID, 0) Rows Between 1 Following And Unbounded Following) END "READ_RIGHT" ,
--
CASE WHEN Upper(r.A_NAME) = 'WRITE' THEN r.A_NAME
ELSE FIRST_VALUE(CASE WHEN Upper(r.A_NAME) = 'WRITE' THEN r.A_NAME END)
OVER(Partition By u.ID Order By u.ID, Nvl(r.ID, 0) Rows Between 1 Following And Unbounded Following) END "WRITE_RIGHT"
FROM users u
LEFT JOIN users_rights ur ON(u.ID = ur.USER_ID)
LEFT JOIN rights r ON(r.ID = ur.RIGHT_ID)
ORDER BY u.ID, r.ID
)
WHERE RN = 1
The subquery checks if any particular user has read, write or both rights placing them in the user's 1st row. Main query just concatinates values giving them true or false if they exist or not.
With your sample data:
WITH
users AS
( Select 1 "ID", 'test1' "LOGIN" From Dual Union All
Select 2, 'test2' From Dual
),
rights AS
( Select 1 "ID", 'read' "A_NAME" From Dual Union All
Select 2, 'write' From Dual Union All
Select 3, 'write' From Dual
),
users_rights AS
( Select 1 "USER_ID", 1 "RIGHT_ID" From Dual Union All
Select 1, 2 From Dual Union All
Select 1, 3 From Dual Union All
Select 2, 2 From Dual
)
... it would be:
R e s u l t :
LOGIN RIGHTS
----- -----------------------
test1 Read=true;Write=true;
test2 Read=false;Write=true;
Additionaly, if you have a user that is not present in users_rights table, let's say user ID=3 LOGIN=test3, then this will return another row like:
LOGIN RIGHTS
----- -----------------------
test1 Read=true;Write=true;
test2 Read=false;Write=true;
test3 Read=false;Write=false;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论