如何编写简单的选择查询而不是使用视图?

Posted

技术标签:

【中文标题】如何编写简单的选择查询而不是使用视图?【英文标题】:How do I write a simple select query instead of using views? 【发布时间】:2012-05-05 13:02:28 【问题描述】:

查找在同一天拜访过同一专业的两位不同医生的患者。

示例数据库:Click here to view the sample data script in SQL Fiddle.

CREATE VIEW DistinctVisits AS
SELECT v.vid,v.pid,d.speciality,v.date
FROM Visits v ,Doctors d
WHERE d.did=v.did
GROUP BY v.pid,v.did,v.date;

CREATE VIEW DistinctVisits2 AS
SELECT dv.pid,dv.speciality,dv.date, COUNT(dv.vid) as countv
FROM DistinctVisits dv
GROUP BY dv.pid,dv.speciality,dv.date;

SELECT dv2.pid,dv2.speciality
FROM DistinctVisits2 dv2
WHERE dv2.countv=2;

DROP VIEW DistinctVisits;
DROP VIEW DistinctVisits2;

我如何在一个大查询中重复相同的想法? 其他解决方案也不错,但请先帮助我改进这个解决方案。

【问题讨论】:

我认为这是一项任务?奇怪的是,我很确定我记得几个月前曾帮助过同样的问题。我想知道是否有人上过同样的大学课程... 【参考方案1】:

说明:

您需要查找在给定日期访问过同一专业的两位不同医生的患者列表。

在此要求中,您的 Patient 表成为主表。让我们先查询该表。

现在我们有了患者名单。我们需要得到他们访问过的医生的名单。我们不能简单地将患者表与医生表连接起来,因为没有用于映射数据的列。我们必须使用 Visits 作为中间表

在 Patient 和 Visits 表之间添加 LEFT OUTER JOIN,并通过 pid 列加入。

我们有患者及其就诊名单,但现在我们需要获取医生信息。因此,在 Visits 和 Doctors 表之间添加另一个 LEFT OUTER JOIN 并通过 did 列加入。

我们有患者和医生就诊信息。但是,我们只需要患者的姓名、就诊医生的专业和就诊日期。因此,我们将在 SELECT 子句和 GROUP BY 子句中添加列 p.pnamed.specialityv.date。除此之外,我们需要所有访问次数,但有一个问题。我们只需要 DISTINCT 计数,换句话说,我们需要他们访问过的所有唯一医生的计数。因此,如果患者在一天内两次拜访同一位医生,则应计为 1。因此,添加 DISTINCT 会有所帮助。此外,关键是使用正确的列名,在这种情况下 d.did 代表医生。

我们拥有所需的所有数据,但我们只需要过滤在同一天拜访过两位不同医生的患者。为此,HAVING 子句可以帮助我们。当您应用 GROUP BY 时,HAVING 是合适的。我们将使用相同的 COUNT(DISTINCT d.did) 来检查计数是否仅与 2 的值匹配。您可以在输出中看到结果。

建议:

您不必为插入到表中的每个值指定 INSERT INTO 语句。您可以将它们放在括号内,并用 commas 将它们分开。最后一条语句应以 semicolon 结尾。

查询使用LEFT OUTER JOIN。我使用此联接找出每位患者的所有就诊次数即使他们从未看过医生。我只是想在形成查询时查看输出。您可以将其更改为 INNER JOIN,我认为这更适合您的场景。

如果您不想显示访问次数,可以将其从 SELECT 子句中删除。

演示:

Click here to view the demo in SQL Fiddle.

解释中使用的脚本:

SELECT          p.pname
            ,   d.speciality 
            ,   v.date
            ,   COUNT(DISTINCT d.did) AS visitcount
FROM            Patient p
LEFT OUTER JOIN Visits v
ON              v.pid = p.pid
LEFT OUTER JOIN Doctors d
ON              d.did = v.did
GROUP BY        p.pname
            ,   d.speciality
            ,   v.date
HAVING          COUNT(DISTINCT d.did) = 2

更适合你的脚本:

SELECT      p.pname
        ,   d.speciality 
        ,   v.date
FROM        Patient p
INNER JOIN Visits v
ON          v.pid = p.pid
INNER JOIN Doctors d
ON          d.did = v.did
GROUP BY    p.pname
        ,   d.speciality
        ,   v.date
HAVING      COUNT(DISTINCT d.did) = 2

输出:

PNAME      SPECIALITY    DATE       VISITCOUNT
---------  ------------  ---------  -----------
Loch Ness  Assholes      17/9/2012      2
Loch Ness  Orthopedist   13/1/2011      2

创建表并插入脚本:

create table InsuranceCompanies  (
    cid         int,
    cname       varchar(20),
    primary key (cid)
);

create table Patient (
    pid         int,
    pname       varchar(20),
    age         int,
    cid         int,
    gender      char,
    primary     key (pid),
    constraint foreign key (cid) 
        references InsuranceCompanies (cid) 
);

create table Doctors (
    did         int ,
    dname       varchar(20),
    speciality  varchar(20),
    age         int,
    cid         int,
    primary key (did),
    constraint foreign key (cid) 
        references InsuranceCompanies (cid) 
);

create table Visits(
    vid         int,
    pid         int,
    did         int,
    date        varchar(20),
    primary key (vid),
    constraint foreign key (pid) 
        references Patient (pid) ,
    constraint foreign key (did) 
        references Doctors (did)
);

INSERT INTO InsuranceCompanies(cid, cname) VALUES
    ( 1111, 'Harel Inc' ),
    ( 2222, 'Clalit Inc' );

INSERT INTO Doctors ( did, dname, speciality, age, cid) VALUES 
    ( 100, 'Jhonny Depp',       'Heart',        42, 1111 ),
    ( 101, 'Tom Tolan',         'Assholes',     62, 1111 ),
    ( 105, 'Yom Tov',           'Assholes',     52, 1111 ),
    ( 102, 'Lauren Jaime',      'Throat',       27, 2222 ),
    ( 103, 'Gomez Flaurence',   'Legs',         37, 2222 ),
    ( 106, 'David Harpaz',      'Orthopedist',  37, 2222 ),
    ( 107, 'David Schwimmer',   'Orthopedist',  37, 2222 ),
    ( 108, 'Sammy Salut',       'Orthopedist',  37, 1111 );

INSERT INTO Patient ( pid, pname, age, cid,gender) VALUES 
    ( 200, 'Jon Gilmour',       25, 2222, 'm' ),
    ( 206, 'Bon Gilmour',       30, 2222, 'm' ),
    ( 205, 'Jon Gilmour',       22, 2222, 'm' ),
    ( 201, 'Bon Jovy',          21, 2222, 'm' ),
    ( 202, 'Loch Ness',         17, 2222, 'f' ),
    ( 203, 'Lilach Sonin',      12, 1111, 'f' ),
    ( 209, 'Lilach Dba',        34, 1111, 'f' ),
    ( 210, 'Paulina Daf',       32, 1111, 'f' ),
    ( 204, 'Gerry Jalor',       23, 1111, 'm' ),
    ( 208, 'Jerrushalem Jalor', 23, 1111, 'm' );

INSERT INTO Visits ( vid, pid, did, date) VALUES 
    ( 300, 204, 100,    '12/12/2012' ),
    ( 301, 204, 101,    '12/12/2012' ),
    ( 302, 204, 101,    '02/01/2012' ),
    ( 303, 202, 101,    '17/09/2012' ),
    ( 311, 202, 105,    '17/09/2012' ),
    ( 304, 203, 102,    '12/12/2011' ),
    ( 312, 202, 106,    '13/06/2012' ),
    ( 314, 202, 107,    '13/01/2011' ),
    ( 313, 202, 108,    '13/01/2011' ),
    ( 305, 204, 102,    '10/10/2011' ),
    ( 306, 201, 100,    '12/01/2012' ),
    ( 316, 204, 108,    '18/05/2012' ),
    ( 307, 202, 100,    '12/07/2012' ),
    ( 315, 203, 108,    '12/07/2012' ),
    ( 310, 204, 103,    '10/04/2012' ),
    ( 308, 203, 102,    '12/12/2011' ),
    ( 309, 200, 101,    '12/12/2012' );

【讨论】:

非常详细准确的答案,感谢您的插入建议,您太棒了!几个疑惑:为什么你使用左外连接而不是另一种连接?实际上,我认为 Count 字段可以在 select 中忽略不计? 恕我直言,左外连接没有多大意义; pid 是患者表的主键。一个简单的 JOIN 就足够了。医生和访问也类似。 外部匹配的 COUNT() 永远不会非零。 内连接和连接一样吗? 另一种情况是:查找从未看过医生的患者的就诊次数(以及医生的专长)。 @Ofek:不,这个讨论是关于语义和数据建模(间接地)。【参考方案2】:

您需要一个HAVING 子句,以便在按日期和专业分组后找到COUNT(*) = 2。事实上,甚至不需要嵌套。 (我还将您的隐式连接替换为逗号分隔的 FROM 子句和显式 JOIN,这是更受欢迎的现代语法)。

SELECT 
  v.pid,
  d.speciality,
  v.date,
  COUNT(COUNT DISTINCT d.did) AS numvisits
FROM 
  visits v
  JOIN Doctors d ON v.did = d.did
GROUP BY v.pid, d.speciality, v.date
HAVING COUNT(COUNT DISTINCT d.did) = 2
/* Note - depending on your RDBMS, you may
   be able to use the count alias as
HAVING numvisits = 2 
   mysql allows this, for ex, but MS SQL Server doesn't and I think Oracle doesn't */

此处的 SELECT 列表和 GROUP BY 应生成这 3 列的聚合组合的患者 ID、专业、日期和就诊次数。然后,HAVING 子句将其限制为仅对该组有 2 次访问的人。

要从中提取患者,请将其包装在子查询中:

SELECT Patients.* 
FROM Patients JOIN (
  SELECT 
    v.pid,
    d.speciality,
    v.date,
    COUNT(COUNT DISTINCT d.did) AS numvisits
  FROM 
    visits v
    JOIN Doctors d ON v.did = d.did
  GROUP BY v.pid, d.speciality, v.date
  HAVING COUNT(COUNT DISTINCT d.did) = 2
) subq ON Patients.pid = subq.pid

【讨论】:

不行,在小提琴上试一下,你会得到不同的结果,然后我的查询......你一定错过了什么 顺便说一句,你在专业中缺少一个 i @OfekRon 是的,改为COUNT(DISTINCT d.did) @OfekRon btw,specialty 是公认的美国拼写,没有其他 i,我为自己辩护。 O/T:我认为这两个词都是有效的。在英国,医生拥有专业的专业领域,而厨师则拥有当天的专业。在这两种情况下,让它们反过来听起来都是错误的,imo。【参考方案3】:

Siva 回答后有点晚了,但这是我的问题:

SELECT v.pid, d.speciality, count(DISTINCT d.did) as cnt
  FROM Visits v
  JOIN Doctors d ON v.did = d.did
 GROUP BY v.pid, d.speciality, v.date
HAVING count(DISTINCT d.did) = 2;

它产生与初始 OP 没有视图的查询完全相同的输出。

【讨论】:

【参考方案4】:
WITH
dv1 as
(
    SELECT v.vid, v.pid, d.speciality, v.date
    FROM Visits v, Doctors d
    WHERE d.did=v.did
    GROUP BY v.pid,v.did,v.date;
),
dv2 as
(
    SELECT dv.pid,dv.speciality,dv.date, COUNT(dv.vid) as countv
    FROM dv1
    GROUP BY dv.pid,dv.speciality,dv.date;
)
SELECT dv2.pid, dv2.speciality
FROM dv2
WHERE dv2.countv=2;

【讨论】:

嗯...当我看到它时是 SQL :) np。我辩论过要删除我的,因为您的回答非常正确,但是问题的性质表明操作背景不是很强。我提出的东西是一个渐进的步骤,可能是更容易理解的垫脚石。所以我离开了。【参考方案5】:

你可以使用嵌套的 CTE(如果你不在 mysql 上,那就是……)一个 CTE 可以看作是一个即时视图,它只在语句的持续时间内存在。 (注:未经测试)

WITH DistinctVisits2 AS (
    WITH DistinctVisits AS (
        SELECT v.vid,v.pid,d.speciality,v.date
        FROM Visits v
        JOIN Doctors d ON d.did=v.did
        GROUP BY v.pid,v.did,v.date
        )
    SELECT dv.pid,dv.speciality,dv.date, COUNT(dv.vid) as countv
    FROM DistinctVisits dv
    GROUP BY dv.pid,dv.speciality,dv.date
    )
SELECT dv2.pid,dv2.speciality
FROM DistinctVisits2 dv2
WHERE dv2.countv=2
    ;

【讨论】:

mysql标签是后来添加的。检查修订历史。 MySQL 现在支持嵌套 CTE。示例 ` WITH a AS ( WITH a AS (SELECT 1) SELECT * FROM a ) SELECT * FROM a` 太棒了!一个伟大的举动。八年后。

以上是关于如何编写简单的选择查询而不是使用视图?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 java 中使用 jdbc 编写选择查询需要检查 COLUMN 是不是为 NULL?

我如何将此查询编写为联接而不是相关查询?

在 MySQL 中,如何编写 SQL 来连接两个表而不是一个子查询?

我应该如何在 Rails 中组织复杂的 SQL 视图?

如何隐藏按钮以使其变成透明而不是白色?

需要编写一个查询而不是 5 个查询来获得所有 5 个状态的结果