MySQL Force Fail a statement when querying with an invalid (non existent) foreign key value - mysql

What I am looking for is a way to have a MySQL statement fail when querying table through an invalid index (id) of a foreign key column of that table.
DETAILS:
I have two tales:
cars:
id | username | brand | model | location
reservations:
id | car_id | username | date
The column reservations.car_id is bound to the column cars.id via a foreign key (ON DELETE CASCADE ON UPDATE CASCADE), where the column reservations.car_id is the child of the column cars.id
Wanted Behaviour
What I am trying to do is to have a SQL statement fail when trying to fetch a single or multiple reservation rows using an invalid car_id. The statement should return an empty array when the car_id is valid (a row with that id is present in the cars table), but there are no reservations in the table with that car_id.
I am looking for this behavior as I want to distinguish when a query is successful but simply has no results (so an empty array), and when a query fails (so I would return None). For the sake of my project, when querying reservations via an invalid car_id, I want this to fail and not simply return an empty array.
Actual Behaviour
When I run the statement:
SELECT * FROM reservations WHERE car_id = :car_id
This statement is successful, but when fetching the query results, it simply returns an empty array. I would want this to return null instead.
These are the attempts I have tried before:
SELECT * FROM reservations
JOIN cars ON reservations.car_id = cars.id
WHERE cars.id = :car_id;
This statement is successful but returns an empty array.
SELECT * FROM reservations WHERE car_id = ( SELECT id FROM cars WHERE id = :car_id );
This statement is also successful but returns an empty array.

It's not clear what you mean by fail. If you mean raise an exception and get the calling program to handle it, you'll need a stored procedure or similar code. But this would be a strange and hard-to-maintain way to fulfill your requirement.
If you mean return the value None somewhere in the result set, you can do something like this. It uses the LEFT JOIN ... IS NULL pattern for detecting missing joined rows.
SELECT cars.id,
COALESCE(reservations.car_id, 'No reservations!!!')
FROM cars
LEFT JOIN reservations ON reservations.car_id = cars.id
WHERE cars.id = :car_id;
I'd try to put this into a more useful query, but you did SELECT * so I can't guess at the columns your tables have.

Related

GROUP BY but ignore nulls unless no not-nulls

(I'm simplifying my data for sake of explanation)
I have a denormalized table (basically the result of a join between 3 different tables) like this:
driver_uuid | vehicle_uuid | document_uuid
A driver has many vehicles,
A driver has many documents,
A vehicle has many documents
I want to run a query where there is a row for each unique driver/vehicle pair.
I tried just doing
SELECT * FROM driver_vehicle_documents
GROUP BY
driver_uuid,
vehicle_uuid
This almost works, but with one problem:
A driver that has 1 or more vehicles will show up one more time than the number of vehicles they have. i.e. a driver that has 1 vehicle will show up twice: once for the driver/vehicle pair, and once for driver/null pair.
We want it so that a driver that has 0 vehicles will show up once. A driver with 1 vehicle will show up once, a driver with 2 vehicles will show up twice, etc.
I know the table design is not ideal for this problem, but the table is used for many different purposes and was designed with those considerations. Changing its design is not feasible at this time.
Imagine you have two lists; a list of drivers and a list of vehicles.
You want the complete list of drivers, plus any vehicle records if they exist.
You can accomplish this with a self-outer-join, like this:
SELECT drivers.driver_uuid,
vehicles.vehicle_uuid,
vehicles.document_uuid
FROM driver_vehicle_documents drivers
LEFT JOIN driver_vehicle_documents vehicles ON vehicles.driver_uuid = drivers.driver_uuid
AND vehicles.vehicle_uuid IS NOT NULL
The join will only allow records where there is a vehicle ID. But if none are found, the driver record will still be output (because it's an outer join) and the vehicle and document IDs will just come back as null.
On approach is to handle the NULL values separately. So, basically run your query filtering out NULL values and then add back in the ones you want:
SELECT DISTINCT driver_uuid, vehicle_uuid
FROM driver_vehicle_documents
WHERE vehicle_uuid
UNION ALL
SELECT DISTINCT driver_uuid, NULL
FROM driver_vehicle_documents dvd
WHERE NOT EXISTS (SELECT 1
FROM driver_vehicle_documents dvd2
WHERE dvd2.driver_uuid = dvd.driver_uuid AND
dvd2.vehicle_uuid IS NOT NULL
);
Seems rather straight forward: filter out null values and do not include the third column
Seems rather straight forward: filter out null values and do not include the third column
SELECT driver_uuid, 0
FROM driver_vehicle_documents
WHERE vehicle_uuid is null
GROUP BY
driver_uuid,
vehicle_uuid
UNION ALL
SELECT driver_uuid, vehicle_uuid
FROM driver_vehicle_documents
WHERE vehicle_uuid is not null
GROUP BY
driver_uuid,
vehicle_uuid

mysql query using column values as parameter in query phpMyAdmin

I have a query i have been working on trying to get a specific set of data, join the comments in duplicate phone numbers of said data, then join separate tables based on a common field "entry_id" which also happens to be the number on the end of the word custom_ to pull up that table.
table named list and tables containing the values i want to join is custom_entry_id (with entry_id being a field in list in which i need the values of each record to replace the words in order to pull up that specific table) i need entry_id from the beginning part of my query to stick onto the end of the word custom for every value my search returns to get the fields from that custom table designated for that record. so it will have to do some sort of loop i guess? sorry like i said I am at a loss at this point
this is where i am so far:
SELECT * ,
group_concat(comments SEPARATOR '\r\n\r\n') AS comments_combined
FROM list WHERE `status` IN ("SALEA","SALE")
GROUP BY phone_number
//entry_id is included in the * as well as status
// group concat combines the comments if numbers are same
i have also experimented on test data with doing a full outer join which doesnt really exist. i feel if you can solve the other part for me i can do the joining of the data with a query similar to this.
SELECT * FROM test
LEFT JOIN custom_sally ON test.num = custom_sally.num
UNION
SELECT * FROM test
RIGHT JOIN custom_sally ON test.num = custom_sally.num
i would like all of this to appear with every field from my list table in addition to all the fields in the custom_'entry_id' tables for each specific record. I am ok with values being null for records that have different custom fields. so if record 1 has custom fields after the join of hats and trousers and record 2 has socks and shoes i realize that socks and shoes for record 1 will be null and hats and trousers for record 2 will be null.
i am doing all this in phpmyadmin under the SQL tab.
if that is a mistake please advise as well. i am using it because ive only been working with SQl for a few months. from what i read its the rookie tool.
i might be going about this all wrong if so please advise
an example
i query list with my query i get 20,000 rows with columns like status, phone_number, comments, entry_id, name, address, so on.
now i want to join this query with custom fields in another table.
the problem is the custom tables' names are all linked to the entry_id.
so if entry_id is 777 then the custom table fields are custom_777
my database has over 100 custom tables with specials fields for each record depending on its entry_id.
when i query the records I don't know how to join the custom fields that are entry_id specific to the rest of my data.i will pull up some tables and data for a better example
this is the list table:
this is the custom_"entry_id"
Full Outer Join in MySQL
for info on full outer joins.

mySQL: join not finding data, even though they exist

I have a couple of tables in a mySQL database. For simplicity I'll just show some basic fields:
Table: sources:
sourceID int not null unique primary key
trigger int not null
<other stuff>
Table: sourceBS
id not null unique primary key
sourceID int not null,
name varchar(20),
SourceID in the in the sourceBS table is a foreign key referencing its namesake in sources, with the cascade option. I have tested this: if I delete an entry in sources, the corresponding entry in sourceBS also vanishes. Good.
I want to select some stuff from a join of sources and sourceBS, filtering based on a "sources" property. This should be easy, via a join which, I think, the foreign key should render pretty efficient, so:
SELECT sources.sourceID, sourceBS.*
FROM sources
LEFT JOIN sourceBS ON sources.sourceID = sourceBS.sourceID
WHERE trigger=1;
But when this runs, each row has "NULL" for the values returned from sourceBS, even sourceBS contains entries matching the condition. I can verify this:
SELECT *
FROM sourceBS
WHERE sourceID IN (
SELECT sourceID
FROM sources
WHERE trigger=1
);
Here I get a proper set of results, i.e. non-null values. But, while this works as a proof of concept, it's no good in real life because I want to return a bunch of stuff from the "sources" table as well, and I don't want to have to run multiple queries in order to get what I want.
Returning to the join, if I replace the left join with an inner join, then no results are returned. It is as if, somehow, the "join" is simply not finding any matches in the sourceBS table, and yet they are there as the second query shows.
Why is this happening? I know that this join has a 1:M relationship, sourceBS could have multiple entries for a given entry in sources, but that should be OK. I can test exactly this type of join on other DBs, and it works.
OK, so I've solved this - it wasn't a transaction issue in the end:when I tried it on the original machine, it failed again. It was the order of the join. It appears that in my terminal I had the "ON" clause the other way round to above, that is, I was doing:
... LEFT JOIN sourceBS ON (sourceBS.blockSourceID=sources.sourceID)
which returns all the nulls. If I do it (as in the above code I pasted)
... LEFT JOIN sourceBS ON (sources.sourceID=sourceBS.sourceID
it works. When I tried it the second time last night on a new machine, I'd used the second formulation.
Guess I'd better read up on joins to understand why this happened!

Clients with at least one call per vendor

I have this situation.
I have clients and i have calls
I want yo know if a clien had at least 1 call per date , and if he/she not , i want an array with de dates without call.
client
id name
1 robert
2 nidia
Call
id date id_client
1 2015-01-01 2
2 2015-01-31 1
The id client 1 has not calls all days least 2015-01-01
did you understand?
Firstly, there's going to have to be some mechanism for determining exactly which dates your query checks against. There are theoretically an infinite number of dates during which a given client may not have placed any call! You have to query against a subset of that infinity.
If you're ok with hard-coding in the query a small number of dates to check, then I think this is what you're looking for:
drop table if exists call;
drop table if exists client;
create table client (id int, name varchar(32), primary key (id) );
insert into client (id,name) values (1,'robert'), (2,'nidia');
create table call (id int, d date, client_id int references client(id), primary key (id) );
insert into call (id,d,client_id) values (1,'2015-01-01',(select id from client where name='nidia')), (2,'2015-01-31',(select id from client where name='robert'));
select
cl.name,
array_agg(ds.d) no_call_dates
from
client cl
cross join (select '2015-01-01'::date d union all select '2015-01-15' union all select '2015-01-31') ds
left join call ca on ca.client_id=cl.id and ca.d=ds.d
where
ca.id is null
group by
cl.name
;
Output:
name | no_call_dates
--------+-------------------------
nidia | {2015-01-31,2015-01-15}
robert | {2015-01-15,2015-01-01}
(2 rows)
I've hard-coded and unioned three dates into a single-column "table literal" (if you will) and cross-joined that with the client table, resulting in one row per-client-per-date. That result-set can then be left-joined with the call table on the client id and call date. You can then use the where clause to filter for only rows where the call table failed to join, which produces a result-set of no-call-days, still per-client-per-date. You can then group by the client and use the array_agg() aggregate function to construct the array of the dates on which the client did not have a call.
If you don't want to hard-code the dates, you can prepare a table of dates in advance and select from that in the cross join clause, or select all dates that are defined in the call table (select distinct d from call;), or use some more complex bit of logic to select the dates to check against. In all these cases, you would simply replace the "table literal" with the appropriate subquery.
Edit: Yes, that's very doable. You can use the generate_series() function to generate an integer series and add it to a fixed start date, which results in a date range. This can be done in the cross-join subquery, as I mentioned before. A good approach to avoid repetition is to use a CTE to set the start and end date:
select
cl.name,
array_agg(ds.d order by ds.d) no_call_dates
from
client cl
cross join (with dr as (select '2015-01-01'::date s, '2015-01-31'::date e) select s+generate_series(0,e-s,15) d from dr) ds
left join call ca on ca.client_id=cl.id and ca.d=ds.d
where
ca.id is null
group by
cl.name
;
Output:
name | no_call_dates
--------+-------------------------
nidia | {2015-01-16,2015-01-31}
robert | {2015-01-01,2015-01-16}
(2 rows)
In the above query, I generate three dates, 2015-01-01, 2015-01-16, and 2015-01-31, by using a date range from Jan 1 to Jan 31 with an increment of 15. Obviously for your case you'll probably want an increment of one, but I just used 15 for a nice simple example with only three dates.
Also, I added an order by clause in the array_agg() call, because it's nicer to get it ordered by date, rather than random.

Empty set returned when asking for items not in empty set

I have a query that is behaving in ways I would otherwise not expect.
I have two tables, stagin_users and users. In both tables I have a column called name. In the users table, EVERY value for name is NULL. In staging_users I have 13 rows that do not have a NULL value. I am trying to run a query where I get all users in the staging table whose name is not in the users table.
My query as written is:
SELECT name
FROM staging_users
WHERE name NOT IN (SELECT name FROM users);
As the query is written, I get NO results back. What is the reason for this behavior?
As the users table only has NULL values I know I could say WHERE name IS NOT NULL and I would get the same results, but I want this query to work against the values in the table, which all happen to be NULL.
From the docs:
To comply with the SQL standard, IN returns NULL not only if the expression on the left hand side is NULL, but also if no match is found in the list and one of the expressions in the list is NULL.
And
expr NOT IN (value,...) is the same as NOT (expr IN (value,...)).
Thus as you are SELECTing NULL values.. NOT IN returns NULL.. so no rows match.
You could rectify as so:
SELECT name
FROM staging_users
WHERE name IS NOT NULL
AND name NOT IN (
SELECT name
FROM users
WHERE name IS NOT NULL
);
Or, same logic:
SELECT su.name
FROM staging_users su
LEFT JOIN users u
ON u.name = su.name
AND su.name IS NOT NULL
AND u.name IS NOT NULL;
As an extra note, I would seriously question a data structure that allows users to have NULL names.. your original query will work if this is changed.
NOT EXISTS is usually a better option for this type of query. NOT IN tends to be inherently unsafe when working with potentially NULL values. See the following link for more details.
https://dba.stackexchange.com/questions/17407/why-does-not-in-with-a-set-containing-null-always-return-false-null