我的房子可以有“水槽”、“水龙头”和/或“厕所”固定装置。
我想找到哪些房屋至少拥有列表中的一个固定装置,但缺少其他固定装置并返回缺少的固定装置。
我将所有必需的灯具放在单独的列表中,而不是在数据库中。
例如,如果房子只有一个水槽,我想知道它缺少水龙头和马桶。
数据
房子 | 夹具 |
---|---|
1 | 水槽 |
1 | 水龙头 |
1 | 厕所 |
2 | 水槽 |
2 | 水龙头 |
3 | 水槽 |
结果
房子 | 缺少固定装置 |
---|---|
2 | 厕所 |
3 | 水龙头 |
3 | 厕所 |
一个解决方案是创建所有房屋和所有固定装置的交叉连接表,然后将其与输入表左连接。
enter co
WITH All_Fixtures as (
SELECT distinct Fixture FROM Data ),
Houses as (
SELECT distinct House FROM Data )
SELECT Houses.House , All_Fixtures.Fixture
FROM Houses CROSS JOIN All_Fixtures
LEFT JOIN Data
ON Houses.House = Data.House AND All_Fixtures.Fixture = Data.Fixture
WHERE Data.Fixture IS NULL
ORDER BY Houses.House;
即使在我们的桌子上添加了一些固定装置,它也能工作。
输入数据是由此查询创建的:
-- create
CREATE TABLE Data (
House NUMBER,
Fixture VARCHAR2(10)
);
-- insert
INSERT INTO Data VALUES (1, 'Sink');
INSERT INTO Data VALUES (1, 'Faucet');
INSERT INTO Data VALUES (1, 'Toilet');
INSERT INTO Data VALUES (2, 'Sink');
INSERT INTO Data VALUES (2, 'Faucet');
INSERT INTO Data VALUES (3, 'Sink');
使用连接查找至少具有一个固定装置的房屋,使用交叉连接生成所有组合,最后使用左连接(过滤不匹配)来查找丢失的固定装置:
with fixtures as (
select 'Faucet' fixture from dual union
select 'Sink' from dual union
select 'Toilet' from dual
), house_ids_with_fixtures as (
select distinct h.id
from house_fixture h
join fixtures f on f.fixture = h.fixture
)
select x.*
from (select * from house_ids_with_fixtures cross join fixtures) x
left join house_fixture h on h.id = x.id and h.fixture = x.fixture
where h.id is null
order by 1, 2
参见现场演示。
这是 SQL Server,但最后一个查询就是这个想法。
--SETUP TABLES
declare @types table (
typeId int,
name varchar(50))
declare @data table (
userId int,
typeId int)
--INSERT SAMPLE DATA
insert into @types
values (1, 'Faucet'), (2, 'Toilet'), (3, 'Sink')
insert into @data
values (100, 1), (100, 2), (100, 3), (200, 1), (300, 2), (300, 3)
--GET RESULTS
select distinct d.userId, t.typeId, t.name
from @types t
cross join @data d
where not exists (
select 1
from @data din
where din.userId = d.userId and din.typeId = t.typeId)
order by 1, 2
为了好玩,你也可以使用 MULTISET 来实现,只要你将 STRING_T 定义为 VARCHAR2 的表(choose_len_you_need):
with house_fixture(id, fixture) as (
select 1, 'Sink' From Dual Union
select 1, 'Faucet' From Dual Union
select 1, 'Toilet' From Dual Union
select 2, 'Sink' From Dual Union
select 2, 'Faucet' From Dual Union
select 3, 'Sink' From Dual Union
select 4, 'WiFi' From Dual
)
, fixtures(fixture) as (
select 'Faucet' from dual union
select 'Sink' from dual union
select 'Toilet' from dual
)
, sets(id, diff) as (
select id, diff from (
select
id,
(
cast(multiset (select fixture from fixtures) as string_t)
multiset except
cast(multiset (select fixture from house_fixture hf1 where hf1.id = hf.id) as string_t)
) as diff
from
house_fixture hf
group by id
)
where cardinality(diff) between 1 and (select count(*)-1 from fixtures)
)
select s.id, t.column_value
from sets s, table(s.diff) t
;
编辑答案(问题编辑后)
使用 Case 表达式创建一个 cte,以获取具有 ID 的行中的所有 3 个灯具,并对缺少灯具的所有行进行 UNION...
甲骨文:
Create Table table1 -- S a m p l e D a t a :
AS
( Select 1 as ID, 'Sink' as FIXTURE From Dual Union All
Select 1, 'Faucet' From Dual Union All
Select 1, 'Toilet' From Dual Union All
Select 2, 'Sink' From Dual Union All
Select 2, 'Faucet' From Dual Union All
Select 3, 'Sink' From Dual Union All
Select 4, 'WiFi' From Dual
);
WITH
grid AS
( Select t1.ID,
Max(Case When t1.FIXTURE = 'Faucet' Then 1 Else 0 End) as FAUCET,
Max(Case When t1.FIXTURE = 'Sink' Then 1 Else 0 End) as SINK,
Max(Case When t1.FIXTURE = 'Toilet' Then 1 Else 0 End) as TOILET
From table1 t1
Where t1.FIXTURE IN('Faucet', 'Sink', 'Toilet')
Group By t1.ID
)
-- S Q L :
Select ID, 'Faucet' as MISSING_FIXTURE From grid Where FAUCET = 0 Union All
Select ID, 'Sink' as MISSING_FIXTURE From grid Where SINK = 0 Union All
Select ID, 'Toilet' as MISSING_FIXTURE From grid Where TOILET = 0
Order By ID
/* R e s u l t :
ID MISSING_FIXTURE
-- ---------------
2 Toilet
3 Faucet
3 Toilet */
在编辑问题之前先回答
一种选择是将您的一组固定装置与每个房屋 ID 的桌子固定装置列表进行比较。后者的创建取决于您使用的 RDBMS(Oracle 的 LISTAGG(),MySql 的 GROUP_CONCAT(),Postgres 的 STRING_AGG(),...)
...定义您的灯具组以与...进行比较
WITH -- G r o u p d e f i n i t i o n :
fixture_group ( GROUP_ID, FIXTURE_GROUP) AS
( Select 1, 'Faucet, Sink, Toilet' as FIXTURE From Dual )
...比较列表并从组中删除现有灯具、逗号字符和前导/尾随空格字符。如果留下 null 则不会丢失任何内容 ( '-' ) ...
-- M a i n S Q L :
Select Distinct t1.ID,
TRIM( REPLACE(
COALESCE( REPLACE( fg.FIXTURE_GROUP,
LISTAGG(t1.FIXTURE, ', ') WITHIN GROUP
(Order By t1.FIXTURE) Over(Partition By t1.ID),
''),
'-'),
',', '') ) as MISSING_FIXTURE_LIST
From table1 t1
Inner Join fixture_group fg ON( INSTR(fg.FIXTURE_GROUP, t1.FIXTURE) > 0 )
Order By t1.ID;
/* R e s u l t :
ID MISSING_FIXTURE_LIST
-- --------------------
1 -
2 Toilet
3 Faucet Toilet */