MySQL: select default row (not default values) if other row not available - sql

edit:
I've included the create statements and a small set of test data for you to try out. Therefor, I've changed the example id to 2 in stead of 5 to represent an existing id in the test data.
/ edit
I have three MySQL tables for keeping localized page info:
CREATE TABLE `locale` (
`languageCode` char(3) NOT NULL,
`regionCode` char(3) NOT NULL default 'ZZ',
`isActive` enum('yes','no') NOT NULL default 'no',
PRIMARY KEY (`languageCode`,`regionCode`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `page` (
`id` int(10) unsigned NOT NULL auto_increment,
`parentId` int(10) unsigned default NULL,
PRIMARY KEY (`id`),
KEY `FK_page_page_1` (`parentId`),
CONSTRAINT `FK_page_page_1` FOREIGN KEY (`parentId`) REFERENCES `page` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `pageInfo` (
`pageId` int(10) unsigned NOT NULL,
`languageCode` char(3) NOT NULL,
`regionCode` char(3) NOT NULL default 'ZZ',
PRIMARY KEY (`pageId`,`languageCode`,`regionCode`),
KEY `FK_pageInfo_locale_1` (`languageCode`,`regionCode`),
KEY `FK_pageInfo_page_1` (`pageId`),
CONSTRAINT `FK_pageInfo_locale_1` FOREIGN KEY (`languageCode`, `regionCode`) REFERENCES `locale` (`languageCode`, `regionCode`) ON DELETE CASCADE,
CONSTRAINT `FK_pageInfo_page_1` FOREIGN KEY (`pageId`) REFERENCES `page` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
And here is some test data:
/* locale */
INSERT INTO `locale` (languageCode,regionCode,isActive) VALUES ('de','ZZ','yes');
INSERT INTO `locale` (languageCode,regionCode,isActive) VALUES ('en','ZZ','yes');
INSERT INTO `locale` (languageCode,regionCode,isActive) VALUES ('nl','ZZ','yes');
/* page */
INSERT INTO `page` (id,parentId) VALUES (1,NULL);
INSERT INTO `page` (id,parentId) VALUES (2,1);
/* pageInfo */
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (1,'de','ZZ');
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (1,'en','ZZ');
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (1,'nl','ZZ');
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (2,'en','ZZ');
INSERT INTO `pageInfo` (pageId,languageCode,regionCode) VALUES (2,'nl','ZZ');
The dilemma:
To retrieve pages with id 2 of all active locales I issue the following SQL statement:
SELECT
p.*, pi.languageCode, pi.regionCode
FROM
page AS p
INNER JOIN pageInfo AS pi
ON pi.pageId = p.id
INNER JOIN locale AS l
ON l.languageCode = pi.languageCode AND l.regionCode = pi.regionCode
WHERE
(p.id = '2')
AND (l.isActive = 'yes')
How would I alter this statement, so that, when id 2 is not available for a particular locale, it automatically falls back on the root page for that locale (that is: where page.parentId IS NULL)? My goal is to have MySQL give me either one, but not both, for the active locales.
I tried:
WHERE
(p.id = '2' OR (p.parentId IS NULL))
But, of course, that gives me two records for locales that actually have id 2 also. I'm pretty sure it's possible (either with UNION, sub selects, or a duplicate join on page) but I'm having a total SQL writers block here. I'd appreciate your help.

How about XOR? "one or the other but not both":
WHERE
(p.id = '5' XOR (p.parentId IS NULL))
^---here

You could order by p.id=5 and use a limit of 1. It's a bit of a hack, but it'll work.
SELECT
p.*, pi.languageCode, pi.regionCode
FROM
page AS p
INNER JOIN pageInfo AS pi
ON pi.pageId = p.id
INNER JOIN locale AS l
ON l.languageCode = pi.languageCode AND l.regionCode = pi.regionCode
WHERE
(p.id = '5' OR (p.parentId IS NULL))
AND (l.isActive = 'yes')
ORDER BY p.id = '5' DESC
LIMIT 1

Use LEFT JOINs and join both tables twice. The second time you join them specifically for the case of p.parentId IS NULL.
In the SELECT list use IFNULLs. First arguments are for the id specified, second ones are the fallbacks, i.e. the values for the root page.
SELECT
p.*,
IFNULL(l.languageCode, lr.LanguageCode) AS languageCode,
IFNULL(l.regionCode, lr.regionCode) AS regionCode
FROM
page AS p
LEFT JOIN pageInfo AS pi
ON pi.pageId = p.id
LEFT JOIN locale AS l
ON l.languageCode = pi.languageCode AND l.regionCode = pi.regionCode
LEFT JOIN pageInfo AS pir
ON pir.pageId = p.id AND p.parentId IS NULL
LEFT JOIN locale AS lr
ON lr.languageCode = pir.languageCode AND lr.regionCode = pir.regionCode
WHERE
(p.id = '5' AND l.isActive = 'yes')
OR (p.parentId IS NULL AND lr.isActive = 'yes')

Related

record "new" has no field "user_id" postgreSQL

THIS is what i try and error
postgres=# INSERT INTO cs222p_interchange.Ad(ad_id, plan, content, pic_num, item_id, seller_user_id, placed_date)
postgres-# VALUES ('ADT32457', 'Gold', 'New games available!', 1, 'F7E1N', '4Z5VC', '2022-11-06');
ERROR: record "new" has no field "user_id"
CONTEXT: SQL statement "INSERT INTO TargetedAds(ad_id, user_id)
SELECT NEW.ad_id, NEW.user_id WHERE (
SELECT category
FROM cs222p_interchange.item i
JOIN cs222p_interchange.ad a ON i.item_id = a.item_id
WHERE ad_id = NEW.ad_id AND (buyer_user_id = NEW.user_id OR seller_user_id = NEW.user_id) )
LIKE (
SELECT category
FROM cs222p_interchange.User u
JOIN cs222p_interchange.Categories c ON u.user_id = c.user_id
WHERE user_id = NEW.user_id)
ON CONFLICT DO NOTHING"
PL/pgSQL function addad() line 1 at SQL statement
This is the trigger
CREATE FUNCTION AddAd() RETURNS Trigger AS
$$
BEGIN
INSERT INTO TargetedAds(ad_id, user_id)
SELECT NEW.ad_id, NEW.user_id
WHERE (
SELECT *
FROM cs222p_interchange.item i
JOIN cs222p_interchange.ad a ON i.item_id = a.item_id
WHERE ad_id = NEW.ad_id AND (buyer_user_id = NEW.user_id OR seller_user_id = NEW.user_id) )
= (
SELECT category
FROM cs222p_interchange.User u
JOIN cs222p_interchange.Categories c ON u.user_id = c.user_id
WHERE user_id = NEW.user_id)
ON CONFLICT DO NOTHING;
RETURN NEW;
END;
$$
LANGUAGE PLPGSQL;
CREATE TRIGGER TargetedAdsLogger AFTER INSERT ON cs222p_interchange.Ad FOR EACH ROW EXECUTE FUNCTION AddAd();
This is the Table
CREATE TABLE TargetedAds(
ad_id text,
user_id text,
PRIMARY KEY (ad_id, user_id),
FOREIGN KEY (ad_id) REFERENCES cs222p_interchange.Ad
(ad_id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES cs222p_interchange.Seller(user_id) ON DELETE CASCADE
);
This is what i need to insert
INSERT INTO cs222p_interchange.Ad(ad_id, plan, content, pic_num, item_id, seller_user_id, placed_date)
VALUES ('ADT32457', 'Gold', 'New games available!', 1, 'F7E1N', '4Z5VC', '2022-11-06');
This is the definition of the Ad table:
CREATE TABLE cs222p_interchange.Ad(
ad_id text NOT NULL,
plan text NOT NULL ,
content text ,
pic_num int NOT NULL,
item_id text NOT NULL,
seller_user_id text NOT NULL,
placed_date date NOT NULL,
PRIMARY KEY (ad_id),
FOREIGN KEY(item_id) REFERENCES cs222p_interchange.Item(item_id) ON DELETE CASCADE,
FOREIGN KEY(pic_num, item_id) REFERENCES cs222p_interchange.Picture(pic_num, item_id) ON DELETE CASCADE,
FOREIGN KEY(seller_user_id) REFERENCES cs222p_interchange.Seller(user_id) ON DELETE CASCADE
);
I have checked multiple times and all of my tables and columns are existing.

SQL - datetime input doesn't take the time

At the moment I got theses pieces of code;
Create:
CREATE TABLE Vlucht(
Vlucht_ID int IDENTITY(1,1),
Lid_ID int,
Vliegtuig_ID int,
VL_Vertrektijd datetime,
VL_Eindtijd datetime,
VL_Type char(1),
Vl_Notitie varchar(max),
CONSTRAINT PK_Vlucht PRIMARY KEY (Vlucht_ID),
CONSTRAINT FK_Vlucht_Ref_Lid FOREIGN KEY (Lid_ID) REFERENCES Lid (Lid_ID) ON UPDATE CASCADE,
CONSTRAINT FK_Vlucht_Ref_Vliegtuig FOREIGN KEY (Vliegtuig_ID) REFERENCES Vliegtuig (Vliegtuig_ID) ON UPDATE CASCADE,
CONSTRAINT CHK_VL_type CHECK (VL_type = 'R' OR VL_type = 'L')
)
go
Insert:
INSERT INTO Vlucht
VALUES (4, 1, '10-04-2018 14:34', '10-04-2018 15:10', 'R', 'Vlucht van Linda'),
(5, 1, '10-04-2018 14:34', '10-04-2018 15:10', 'R', 'Vlucht van Jaap')
Select:
SELECT FORMAT(VL_Vertrektijd, N'MM-dd-yyyy HH:mm'), +
FORMAT(VL_Eindtijd, N'MM-dd-yyyy HH:mm'),
VL_Type, Vl_Notitie, V_Naam, P_Voornaam, P_Achternaam FROM Vlucht vl
INNER JOIN Vliegtuig v
ON v.Vliegtuig_ID = vl.Vliegtuig_ID
INNER JOIN Lid l
ON l.Lid_ID = vl.Lid_ID
INNER JOIN Persoon p
ON p.Persoon_ID = l.Lid_ID
Result:
10-04-2018 00:00
So it does take the date, but it refuses to input the time given. The problem probably lies within some casting of format problems.

Converting strange SQL Server JOIN syntax to MySQL syntax

I have a SQL Server query that I am attempting to port to MySQL, but the JOIN syntax is something that I have never seen used before. The query is from a view designed to measure procedure code usage. What the heck is going on with the JOIN syntax just past T.PatID = P.ID, and the third LEFT OUTER JOIN, and what equivalent syntax can we use in MySQL? It does not like this JOIN syntax at all (disregard the ISNULL and CONVERT SQL Server specific syntax)
SELECT
T.Code
, P.LastName
, P.FirstName
, T.TranDate
, CD.DaysUnits
, T.TranAmt
, TD.FullName AS Provider
, ISNULL(TD.ID, ISNULL(AD.ID, PD.ID)) AS DoctorID
FROM
dbo.Doctors AS PD
INNER JOIN
dbo.Transactions AS T
INNER JOIN
dbo.Patients AS P
ON
T.PatID = P.ID
ON
PD.ID = P.DoctorID
LEFT OUTER JOIN
dbo.Doctors AS TD
ON
T.DoctorID = TD.ID
LEFT OUTER JOIN
dbo.Doctors AS AD
LEFT OUTER JOIN
dbo.Appointments
ON
AD.ID = dbo.Appointments.DoctorID
AND CONVERT(varchar(20), dbo.Appointments.ScheduleDateTime, 8) <> '00:00:00'
ON
T.ApptID = dbo.Appointments.ID
LEFT OUTER JOIN
dbo.ChargeDetails AS CD
ON
T.ID = CD.ChargeTranID
WHERE
(
T.Code IS NOT NULL
)
The SHOW CREATE TABLE are as follows
CREATE TABLE Doctors
(
ID int(10) NOT NULL PRIMARY KEY
, FullName varchar(50) DEFAULT NULL
)
CREATE TABLE Patients
(
LName varchar(50) DEFAULT NULL
, FName varchar(50) DEFAULT NULL
, ID int(10) NOT NULL PRIMARY KEY
)
CREATE TABLE Transactions
(
TranType varchar(2) DEFAULT NULL
, Code varchar(100) DEFAULT NULL
, TranSubType varchar(2) DEFAULT NULL
, Description varchar(2000) DEFAULT NULL
, TranDate datetime
, PatID int(10) DEFAULT NULL
, ID int(10) NOT NULL PRIMARY KEY
, TranAmt decimal(19,4) DEFAULT NULL
, ApptID int(10) DEFAULT NULL
, DoctorID int(10) DEFAULT NULL
)
CREATE TABLE ChargeDetails
(
DaysUnits varchar(50) DEFAULT NULL
-- DaysUnits is just an int ranging from 1 to 2
, ChargeTranID int(10) NOT NULL PRIMARY KEY
)
CREATE TABLE Appointments
(
DoctorID int(10) DEFAULT NULL
, PatientID int(10) DEFAULT NULL
, ScheduleDateTime datetime DEFAULT NULL
, ID int(10) NOT NULL PRIMARY KEY
)
Thank you in advance for your help.
Here is a similar (and simplified) query using the same structure as the first query. The second query moves the joins around to make things easier to read.
set nocount on;
use tempdb;
go
declare #doc table (id int not null);
declare #tran table (id int not null, patid int not null);
declare #patients table (id int not null, docid int not null);
insert #doc (id) values (1);
insert #patients (id, docid) values (25, 1);
insert #tran (id, patid) values (100, 25)
select *
from #doc as pd
inner join #tran as t
inner join #patients as p
on t.patid = p.id
on pd.id = p.docid;
select *
from #tran as t
inner join #patients as p
on t.patid = p.id
inner join #doc as pd
on pd.id = p.docid;
Other things look strange. I don't see a need to join to appointments but I'm not going to spend a lot of time to figure out the logic and the schema. The convert usage seems like a bad way to check for null - unless there is a special "flag" datetime value that is used as the equivalent to null. Again, you need to understand the query, the goal of the query, the schema on which it is based, and how the tables are populated. Quite frankly, this code raises concerns about the quality of the entire system.
.

Select form a lot of many-to-many relations at once best practice

I'm wondering what is the best approach to handle the following problem:
I've got a DB-Structure where many tables are linked to my Person table like this:
phone n-n person_phone_realtion n-n person n-n person_email_realtionn-n email
I want to query my tables and parse the result to JSON and store the many to many values inside arrays. Is it better to make only one trip to the database and parse the result of my JOIN-query (see example below), which can be quite large due to duplicates, to my desired schema or should I make more trips to the database and keep the query result small?
What is the best practices for this scenario
Created with the following statement:
DROP TABLE IF EXISTS phone CASCADE;
DROP TABLE IF EXISTS email CASCADE;
DROP TABLE IF EXISTS person CASCADE;
DROP TABLE IF EXISTS person_phone_realtion CASCADE;
DROP TABLE IF EXISTS person_email_realtion CASCADE;
CREATE TABLE phone (
phon_id text NOT NULL,
CONSTRAINT phone_pk PRIMARY KEY (phon_id)
);
CREATE TABLE email (
emai_id text NOT NULL,
CONSTRAINT email_pk PRIMARY KEY (emai_id)
);
CREATE TABLE person (
pers_id INTEGER NOT NULL,
CONSTRAINT person_pk PRIMARY KEY (pers_id)
);
CREATE TABLE person_phone_realtion (
pers_id int NOT NULL,
phon_id int NOT NULL
);
CREATE TABLE person_email_realtion (
pers_id int NOT NULL,
email_id int NOT NULL
);
INSERT INTO person(pers_id)
VALUES (1),(2),(3),(4),(5);
INSERT INTO email(emai_id)
VALUES ('a'),('b'),('c');
INSERT INTO phone(phon_id)
VALUES ('D'),('E'),('F');
INSERT INTO person_email_realtion(pers_id, email_id)
VALUES (1,'a'),(1,'b'), (1,'c'),(2,'b'),(3,'c');
INSERT INTO person_phone_realtion(pers_id, phon_id)
VALUES (1,'D'),(2,'D'), (2,'E'),(5,'F');
DROP TABLE IF EXISTS phone CASCADE;
DROP TABLE IF EXISTS email CASCADE;
DROP TABLE IF EXISTS person CASCADE;
DROP TABLE IF EXISTS person_phone_realtion CASCADE;
DROP TABLE IF EXISTS person_email_realtion CASCADE;
CREATE TABLE phone (
phon_id text NOT NULL,
CONSTRAINT phone_pk PRIMARY KEY (phon_id)
);
CREATE TABLE email (
emai_id text NOT NULL,
CONSTRAINT email_pk PRIMARY KEY (emai_id)
);
CREATE TABLE person (
pers_id INTEGER NOT NULL,
CONSTRAINT person_pk PRIMARY KEY (pers_id)
);
CREATE TABLE person_phone_realtion (
pers_id int NOT NULL,
phon_id int NOT NULL
);
CREATE TABLE person_email_realtion (
pers_id int NOT NULL,
email_id int NOT NULL
);
INSERT INTO person(pers_id)
VALUES (1),(2),(3),(4),(5);
INSERT INTO email(emai_id)
VALUES ('a'),('b'),('c');
INSERT INTO phone(phon_id)
VALUES ('D'),('E'),('F');
INSERT INTO person_email_realtion(pers_id, email_id)
VALUES (1,'a'),(1,'b'), (1,'c'),(2,'b'),(3,'c');
INSERT INTO person_phone_realtion(pers_id, phon_id)
VALUES (1,'D'),(2,'D'), (2,'E'),(5,'F');
Now I can query all the relations at once using JOIN wich would result in a lot of duplicate content:
SELECT * FROM person
RIGHT JOIN person_phone_realtion
ON person.pers_id = person_phone_realtion.pers_id
RIGHT JOIN phone
ON person_phone_realtion.phon_id = phone.phon_id
RIGHT JOIN person_email_realtion
ON person.pers_id = person_email_realtion.pers_id
RIGHT JOIN email
ON person_email_realtion.email_id = email.emai_id;
where I will get a result similar to this:
pers_id phon_id emai_id
1 D a
1 D b
1 D c
2 E b
2 D b
The resulting JSON should look like this:
[
{
"person" : 1,
"email": [
"a", "b", "c"
],
"phone":[
"D"
]
},
{
"person" : 2,
"email": [
"b"
],
"phone":[
"D", "E"
]
}
]
One trip to the database is usually best. You should pre-aggregate the values along each dimension:
select p.*, pp.phones, pe.emails
from person p left join
(select pers_id, array_agg(ppr.phone_id) as phones
from person_phone_realtion ppr
group by pers_id
) pp
on p.pers_id = pp.pers_id left join
(select pers_id, array_agg(per.email_id) as emails
from person_email_realtion ppr
group by pers_id
) pe
on p.pers_id = pe.pers_id ;
You can aggregate into strings or JSON, if you prefer.

Update table using query result

I am attempting to add the result of my query into the column of an existing table.
Thus far, the query below calculates the CAR_PRICE and displays the value. However, I want to add this value to the CAR_PAYMENT_TBL in the car_price column.
The create table commands below show the relevant table and the relationships between them. Is it possible to update the CAR_PRICE value in the CAR_PAYMENT_TBL?
SELECT C.TICKET_NO,
C.REG_ID,
C.BOOKING_ID,
(R.END_DATE-R.START_DATE) AS DAYS_STAYED,
(R.END_DATE-R.START_DATE)*5 AS CAR_PRICE
FROM CAR_TBL C
LEFT JOIN
ROOM_TBL R
ON C.BOOKING_ID = R.BOOKING_ID;
TABLE SCHEMA:
CREATE TABLE CAR_PAYMENT_TBL
(
TICKET_NO INT NOT NULL PRIMARY KEY,
CAR_PRICE NUMERIC(5,2)
);
CREATE TABLE CAR_TBL
(
REG_ID VARCHAR2(7) NOT NULL PRIMARY KEY,
TICKET_NO INT NOT NULL references CAR_PAYMENT_TBL(TICKET_NO),
BOOKING_ID INT NOT NULL references BOOKING_TBL(BOOKING_ID)
);
CREATE TABLE ROOM_TBL
(
STAY_NO INT NOT NULL PRIMARY KEY,
ROOM_NO VARCHAR2(4) NOT NULL references ROOM_DETAILS_TBL(ROOM_NO),
START_DATE DATE NOT NULL,
END_DATE DATE NOT NULL,
BOOKING_ID INT NOT NULL references BOOKING_TBL(BOOKING_ID)
);
You cannot reference other tables in an UPDATE statement in Oracle - use a subquery or a MERGE statement:
UPDATE CAR_PAYMENT_TBL
SET CAR_PRICE =
(select (ROOM_TBL.END_DATE - ROOM_TBL.START_DATE)*5 from room_tbl where ... )
WHERE CAR_PAYMENT_TBL.TICKET_NO = &TICKET_NO;
You'll also have to provide a sensible WHERE clause for the subquery (assuing &TICKET_NO is really a bind variable and not your join condition for the two tables).
If you want to update all the records of CAR_PAYMENT_TBL based on the values of ROOM_TBL
then
UPDATE CAR_PAYMENT_TBL
SET CAR_PRICE = (select (ROOM_TBL.END_DATE – ROOM_TBL.START_DATE)*5
FROM ROOM_TBL WHERE CAR_PAYMENT_TBL.TICKET_NO = ROOM_TBL.TICKET_NO)
If you want to update only specific record of CAR_PAYMENT_TBL then
UPDATE CAR_PAYMENT_TBL
SET CAR_PRICE = (select (ROOM_TBL.END_DATE – ROOM_TBL.START_DATE)*5
FROM ROOM_TBL WHERE CAR_PAYMENT_TBL.TICKET_NO = ROOM_TBL.TICKET_NO)
where CAR_PAYMENT_TBL = &ticket_num;