我很想丢失,试图解决问题。起初我有5个表:
CREATE TABLE DOCTOR (
Doc_Number INTEGER,
Name VARCHAR(50) NOT NULL,
Specialty VARCHAR(50) NOT NULL,
Address VARCHAR(50) NOT NULL,
City VARCHAR(30) NOT NULL,
Phone VARCHAR(10) NOT NULL,
Salary DECIMAL(8,2) NOT NULL,
DNI VARCHAR(10) UNIQUE,
CONSTRAINT pk_Doctor PRIMARY KEY (Doc_Number),
CONSTRAINT ck_Salary CHECK (Salary >0)
);
CREATE TABLE PATIENT (
Pat_Number INTEGER,
Name VARCHAR(50) NOT NULL,
Address VARCHAR(50) NOT NULL,
City VARCHAR(30) NOT NULL,
DNI VARCHAR(10) UNIQUE,
CONSTRAINT pk_PATIENT PRIMARY KEY (Pat_Number)
);
CREATE TABLE VISIT (
Doc_Number INTEGER,
Pat_Number INTEGER,
Visit_Date DATE,
Price DECIMAL(7,2),
Last_Drug VARCHAR(50),
CONSTRAINT Visit_pk PRIMARY KEY (Doc_Number, Pat_Number, Visit_Date),
CONSTRAINT ck_Price CHECK (Price >0),
CONSTRAINT Visit_Doctor_fk FOREIGN KEY (Doc_Number) REFERENCES DOCTOR(Doc_Number),
CONSTRAINT Visit_PATIENT_fk FOREIGN KEY (Pat_Number) REFERENCES PATIENT(Pat_Number)
);
CREATE TABLE PRESCRIPTION (
Presc_Number INTEGER,
Drug VARCHAR(50) NOT NULL,
Doc_Number INTEGER NOT NULL,
Pat_Number INTEGER NOT NULL,
Visit_Date DATE NOT NULL,
CONSTRAINT Prescription_pk PRIMARY KEY (Presc_Number),
CONSTRAINT Prescription_Visit_fk FOREIGN KEY (Doc_Number, Pat_Number, Visit_Date) REFERENCES VISIT(Doc_Number, Pat_Number, Visit_Date)
);
CREATE TABLE VISITS_SUMMARY (
Doc_Number INTEGER,
Pat_Number INTEGER,
Year INTEGER,
Drugs_Number INTEGER,
Visits_Number INTEGER,
Acum_Amount DECIMAL(8,2),
Last_Drug VARCHAR(50),
CONSTRAINT ck_Visits_Number CHECK (Visits_Number >0),
CONSTRAINT ck_Acum_Amount CHECK (Acum_Amount >0),
CONSTRAINT Visits_Summary_pk PRIMARY KEY (Doc_Number, Pat_Number, Year),
CONSTRAINT Summary_Doctor_fk FOREIGN KEY (Doc_Number) REFERENCES DOCTOR(Doc_Number),
CONSTRAINT Summary_PATIENT_fk FOREIGN KEY (Pat_Number) REFERENCES PATIENT(Pat_Number)
);
我填写了前4个,我需要创建一个函数来更新最后一个。该功能必须:
另外,我需要考虑这些可能的错误:
最后将信息保存在VISITS_SUMMARY
表中。
我使用return和works在不同的函数中分别创造了第一个4分:
CREATE OR REPLACE FUNCTION sum_visits (p_Doc_Number INTEGER, p_Pat_Number INTEGER, p_Year INTEGER)
RETURNS INTEGER AS $$
DECLARE
BEGIN
SELECT COUNT(Drug)INTO drugs_num
FROM PRESCRIPTION pr
WHERE pr.Doc_Number = p_Doc_Number AND pr.Pat_Number = p_Pat_Number AND
(SELECT EXTRACT(YEAR FROM pr.Visit_Date)) = p_Year;
RETURN drugs_num;
END;
$$LANGUAGE plpgsql;
和其他使用相同的函数参数一样只改变返回类型。
SELECT COUNT(Visit_Date)INTO visits
FROM VISIT v
WHERE v.Doc_Number = p_Doc_Number AND v.Pat_Number = p_Pat_Number AND
(SELECT EXTRACT(YEAR FROM v.Visit_Date)) = p_Year;
total_price = 0.0;
FOR visit_price IN SELECT Price FROM VISIT v
WHERE v.Doc_Number = p_Doc_Number AND v.Pat_Number = p_Pat_Number AND
(SELECT EXTRACT(YEAR FROM v.Visit_Date)) = p_Year LOOP
total_price := total_price + visit_price;
END LOOP;
SELECT Drug INTO last_drg FROM PRESCRIPTION pr
WHERE pr.Doc_Number = p_Doc_Number AND pr.Pat_Number = p_Pat_Number AND
(SELECT EXTRACT(YEAR FROM pr.Visit_Date)) = p_Year AND
Presc_Number = (SELECT MAX(Presc_Number)FROM PRESCRIPTION);
我尝试使用IF
条件执行例外但不起作用。以下是该函数的一个不同操作的完整示例:
CREATE OR REPLACE FUNCTION sum_visits (p_Doc_Number INTEGER, p_Pat_Number INTEGER, p_Year INTEGER)
RETURNS void AS $$
DECLARE
drugs_num INTEGER;
BEGIN
IF (PRESCRIPTION.Doc_Number NOT IN (p_Doc_Number))THEN
RAISE EXCEPTION
'Doctor % doesn"t exists';
ELSIF (PRESCRIPTION.Pat_Number NOT IN (p_Pat_Number))THEN
RAISE EXCEPTION
'Patient doesn"t exists';
ELSIF((SELECT EXTRACT(YEAR FROM PRESCRIPTION.Visit_Date)) NOT IN p_Year) THEN
RAISE EXCEPTION
'Date % doesn"t exists'
ELSE SELECT COUNT(Drug)INTO drugs_num
FROM PRESCRIPTION pr
WHERE pr.Doc_Number = p_Doc_Number AND pr.Pat_Number = p_Pat_Number AND
(SELECT EXTRACT(YEAR FROM pr.Visit_Date)) = p_Year;
end if;
update VISITS_SUMMARY
set Drugs_Number = drugs_num;
exception
when raise_exception THEN
RAISE EXCEPTION' %: %',SQLSTATE, SQLERRM;
END;
$$LANGUAGE plpgsql;
我需要帮助才能使用update语句,因为即使不考虑异常,表格也不会更新,并且对控件异常有所帮助。
有一些示例可以填充第一个表并使用此参数调用函数(26902,6574405,2011)
INSERT INTO DOCTOR (Doc_number,Name,Specialty,Address,City,Phone,Salary,DNI) values
(26902,'Dr. Alemany','Traumatologia','C. Muntaner, 55,','Barcelona','657982553',71995,'52561523T');
INSERT INTO PATIENT (Pat_Number, Name, Address, City, DNI) values
(6574405,'Sra. Alemany ','C. Muntaner, 80','Barcelona','176784267B');
INSERT INTO VISIT (Doc_Number, Pat_Number,Visit_Date,Price) values
(26902,6574405,'30/03/11',215);
INSERT INTO PRESCRIPTION (Presc_Number, Drug, Doc_Number, Pat_Number, Visit_Date) values
(44,'Diclofenac',26902,6574405,'30/03/11')
, (45,'Ibuprofè',26902,6574405,'30/03/11')
, (46,'Ibuprofè',26902,6574405,'30/03/11');
如果你愿意,我有更多的插页。
答案 0 :(得分:2)
以下是我假设完成工作的功能。
可能需要一些解释,所以这里是:
构造 f_start_of_year
和f_end_of_year
以进行查询 sargable (能够使用索引来加速它的执行),因为函数是黑盒子Postgres优化器,因此执行查询WHERE function(visit_date) ...
无法使用在列visit_date
上声明的索引。对于这种特殊情况,您需要在to_char(visit_date, 'YYYY')
上编制索引,例如将2011
作为字符结果。拥有一个索引并调整查询比使用其他方法更好。另一方面,Postgres确实很快评估了运算符的右侧,而左侧保持不变,因此它与索引条件匹配。
在开始的时候,我们正在检查是否存在医生,患者和就诊。
如果您想更新所有不同医生,患者记录的统计数据,那么呼叫可能看起来像
SELECT sum_visits(doc_number, pat_number, 2011)
FROM (
SELECT doc_number, pat_number
FROM visit
GROUP BY 1,2
) foo;
在计算我放置COUNT(DISTINCT drug)
的药物数量时,因为您已经说过想要计算不同药物的存在(所以如果医生为某个特定患者开了两次药物,那么它只会计为1.要删除此行为,只需删除DISTINCT
子句。
考虑将RAISE EXCEPTION
替换为RAISE NOTICE
和RETURN
子句 - 请参阅手册以供参考。提高EXCEPTION
会阻止进一步执行函数。
然后一个函数建立必须完成的操作INSERT/UPDATE
- 因为您可能希望比一年更频繁地计算统计数据,因此INSERT
语句将失败visits_Summary_pk
< / p>
至于RETURN
值 - 您可以获得该功能的确切信息以及针对特定行更新/插入的统计信息。这样你就可以做一些日志记录。它还可以帮助您进行调试。
CREATE OR REPLACE FUNCTION sum_visits (p_doc_number INTEGER, p_pat_number INTEGER, p_year INTEGER)
RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
f_start_of_year date := p_year || '-01-01';
f_end_of_year date := (p_year || '-01-01')::DATE + '1 year - 1 day'::INTERVAL;
f_drug_count integer := 0;
f_visits_count integer := 0;
f_price_sum decimal(8,2) := 0.00;
f_last_drug varchar(50);
f_check_if_record_exists boolean;
BEGIN
-- Checking
IF (SELECT count(*) FROM doctor WHERE doc_number = p_doc_number) = 0 THEN
RAISE EXCEPTION 'Doctor % does not exist', p_doc_number;
END IF;
IF (SELECT count(*) FROM patient WHERE pat_number = p_pat_number) = 0 THEN
RAISE EXCEPTION 'Patient % does not exist', p_doc_number;
END IF;
IF (SELECT count(*) FROM visit WHERE doc_number = p_doc_number AND pat_number = p_pat_number AND visit_date BETWEEN f_start_of_year AND f_end_of_year) = 0 THEN
RAISE EXCEPTION 'There are no visits for doctor %, patient % in year %', p_doc_number, p_pat_number, p_year;
END IF;
SELECT COUNT(DISTINCT drug) INTO f_drug_count
FROM prescription WHERE doc_number = p_doc_number AND pat_number = p_pat_number AND visit_date BETWEEN f_start_of_year AND f_end_of_year;
SELECT COUNT(*) INTO f_visits_count
FROM visit WHERE doc_number = p_doc_number AND pat_number = p_pat_number AND visit_date BETWEEN f_start_of_year AND f_end_of_year;
SELECT SUM(price) INTO f_price_sum
FROM visit WHERE doc_number = p_doc_number AND pat_number = p_pat_number AND visit_date BETWEEN f_start_of_year AND f_end_of_year;
SELECT drug INTO f_last_drug
FROM prescription WHERE doc_number = p_doc_number AND pat_number = p_pat_number AND visit_date BETWEEN f_start_of_year AND f_end_of_year ORDER BY visit_date DESC, presc_number DESC LIMIT 1;
SELECT CASE WHEN COUNT(*) > 0 THEN true ELSE false END INTO f_check_if_record_exists FROM visits_summary WHERE doc_number = p_doc_number AND pat_number = p_pat_number AND year = p_year;
IF (f_check_if_record_exists = 'f') THEN
INSERT INTO visits_summary(doc_number, pat_number, year, drugs_number, visits_number, acum_amount, last_drug)
VALUES (p_doc_number, p_pat_number, p_year, f_drug_count, f_visits_count, f_price_sum, f_last_drug);
ELSE
UPDATE visits_summary SET
drugs_number = f_drug_count, visits_number = f_visits_count, acum_amount = f_price_sum, last_drug = f_last_drug
WHERE doc_number = p_doc_number AND pat_number = p_pat_number AND year = p_year;
END IF;
RETURN CONCAT(CASE f_check_if_record_exists WHEN true THEN 'Updated' ELSE 'Inserted into' END || ' visits_summary for Doctor_ID: ',p_doc_number,' / Patient_ID: ',p_pat_number,' / Year: ',p_year,E'\n','WITH VALUES: drug_count: ',f_drug_count,', visits_count: ',f_visits_count,', price_sum: ',f_price_sum,', last_drug: ',COALESCE(f_last_drug,'none'));
END;
$$;
一些一般提示:
varchar(n)
作为列类型。 varchar(n)
和varchar
之间没有任何性能差异,但稍后更改它的大小可能会对您造成伤害。如果您确实要限制存储在列中的字符,最好使用varchar(n)
和其他CHECK
约束 - 以后很容易对其进行更改。 Read first tip in documentation about character types CHECK
表和visits_summary
上的EXCEPTION
约束。在我看来,最好将它存储在一个值为0的表中,然后更新它,而不是完全没有它(你可以获得更准确的统计数据,如果你想要聚合并通过在里面输入所有行来做任何数学运算一张桌子)visit(visit_date)
上的索引会加快上述函数中使用的查询。 修改:您还可以使用特殊变量FOUND
或NO_DATA_FOUND
替换检查部分代码 - 更多内容here
答案 1 :(得分:1)
有一些有趣的元素。最引人注目的是:
对于主键来说,(Doc_Number, Pat_Number, Visit_Date)
似乎是一个糟糕的主意。如果文档在同一天响两次,那你就搞砸了。而是使用更实用的serial
列作为代理主键:
然后您还可以简化prescription
中的FK:
CREATE TABLE visit (
visit_id serial NOT NULL PRIMARY KEY
, doc_number int NOT NULL
, pat_number int NOT NULL
, visit_date date NOT NULL
, price int -- amount in Cent -- Can be NULL?
, last_drug text -- seems misplaced
, CONSTRAINT ck_price CHECK (price > 0)
, CONSTRAINT visit_doctor_fk FOREIGN KEY (doc_number) REFERENCES doctor
, CONSTRAINT visit_patient_fk FOREIGN KEY (pat_number) REFERENCES patient
);
CREATE TABLE prescription (
presc_number int PRIMARY KEY -- might also be a serial?
, visit_id int NOT NULL REFERENCES visit
, drug text NOT NULL
);
在进行此操作时,我正在制作price
代表Cents的integer
列。那要便宜得多。显示为€很简单,我将在下面的VIEW
中进行演示。
您是否知道CHECK
约束ck_Price CHECK (Price > 0)
允许NULL值?可能与预期一样。
目前接受的答案有很多好处。但并非所有这些都是好的。建议的功能复杂且效率低,可以大大简化。
更重要的是,手工编织解决方案的整个想法是可疑的,容易出错,昂贵且复杂。您遇到了典型的UPSERT
problem并且没有适合并发使用的解决方案。 (A clean solution is under development并且可能会或可能不会附带Postgres 9.5。)
但这对你来说都不是必需的......
VIEW
我强烈建议您考虑使用VIEW
替换您的表格VISITS_SUMMARY
,或者如果您的表格较大且需要阅读效果,请MATERIALIZED VIEW
。那你根本就不需要一个功能。基于我上面建议的改进:
提取(年份来自v.visit_date)AS年
CREATE MATERIALIZED VIEW AS
SELECT DISTINCT ON (1,2,3)
v.doc_number
, v.pat_number
, extract(year FROM v.visit_date) AS year
, count(*) OVER () AS visits
, sum(v.price) OVER () / 100.0 AS acum_amount -- with 2 fractional digits
, sum(p.drugs_count) OVER () AS drugs_count
, v.visit_date AS last_visit
, p.last_drug
FROM visit v
LEFT JOIN (
SELECT DISTINCT ON (1)
visit_id
, drug AS last_drug
, count(*) OVER () AS drugs_count
FROM prescription
ORDER BY 1, presc_number DESC
LIMIT 1
) p USING (visit_id)
ORDER BY 1, 2, 3, v.visit_date DESC;
是一个非常具有误导性的毒品名称,因为您还有Drugs_Number
doc_number
等。使用drugs_count
而不是
严格来说,因为一次就诊可以有多个处方,所以&#34;最后一种药物&#34;很暧昧。从上次访问中选择一个一个任意药物(&#34;最后一个&#34;一个)。
表达式sum(v.price) / 100.0
强制结果自动为numeric
,因为数字常量100.0
(带小数位)is assumed to be numeric automatically。因此,integer
(代表美分)中的price
值以所需格式显示,带有两个小数位(代表€)。
查询有点棘手,因为您需要来自两个表的聚合以及&#34; last&#34;药物。我首先得到最后一个药物并按visit_id
计数,加入visit
并使用窗口函数计算所有聚合,以使用DISTINCT ON
获取上次访问时的最后一种药物。
关于DISTINCT ON
: