SQL查询以查找交叉连接内的唯一出现。

huangapple go评论59阅读模式
英文:

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

fiddle


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;

fiddle


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

fiddle

答案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; 

huangapple
  • 本文由 发表于 2023年3月15日 18:02:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/75743160.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定