Clients with at least one call per vendor - postgresql-8.4

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.

Related

In MySQL, How to Select a Row From A Table Exactly Once to Populate Another Table?

I have a table of seven recipes, each of which needs to be assigned to a student. Each student can be assigned a maximum of one recipe, and there are more total students than total recipes, so some students will not receive any assignment.
In my table of assignments, I need to populate which recipe is assigned to which student. (In my business requirements, assignments must be a freestanding table; I cannot add a column to the recipes table).
Below is the script I am using (including for creating sample data).
I had hoped by using the NOT EXISTS clause, I could prevent a student from being assigned more than one recipe.... but this is not working because the same student is being assigned to every recipe. Any guidance on how to fix my script would be greatly appreciated. Thank you!
/* CREATE TABLE HAVING SEVEN RECIPES */
CREATE TABLE TempRecipes( Recipe VARCHAR(16) );
INSERT INTO TempRecipes VALUES ('Cake'), ('Pie'), ('Cookies'), ('Ice Cream'), ('Brownies'), ('Jello'), ('Popsicles');
/* CREATE TABLE HAVING TEN STUDENTS, i.e. MORE STUDENTS THAN AVAILABLE RECIPES */
CREATE TABLE TempStudents( Student VARCHAR(16) );
INSERT INTO TempStudents VALUES ('Ann'), ('Bob'), ('Charlie'), ('Daphne'), ('Earl'), ('Francine'), ('George'), ('Heather'), ('Ivan'), ('Janet');
/* CREATE TABLE TO STORE THE ASSIGNMENTS */
CREATE TABLE TempAssignments( Recipe VARCHAR(16), Student VARCHAR(16) );
INSERT INTO TempAssignments( Recipe, Student )
SELECT TempRecipes.Recipe, ( SELECT S1.Student FROM TempStudents S1 WHERE NOT EXISTS (SELECT TempAssignments.Student FROM TempAssignments WHERE TempAssignments.Student = S1.Student) LIMIT 1 ) Student
FROM TempRecipes;
One way you can consider is making two separate queries, make them as a derived table and assigning a unique identifier on each query that you can match against another. I think that the unique identifier can be a row number.
This suggestion is for MySQL v8+ that supports ROW_NUMBER() function (or if I'm not mistaken; on MariaDB v10.2+?). You've already established these conditions:
Each student can be assigned a maximum of one recipe.
If students count are more than recipes then some students will not receive any recipe assignment.
Let's assume that there's an additional condition:
The recipe assigned will be random.
So, both table will have basically the same query structure as such:
SELECT Student,
ROW_NUMBER() OVER (ORDER BY RAND()) AS Rn1
FROM TempStudents;
SELECT Recipe,
ROW_NUMBER() OVER (ORDER BY RAND()) AS Rn2
FROM TempRecipes;
In that query, the additional condition no.3 of "random assignment" is implemented in the ROW_NUMBER() function. If you run the query as is, you'll almost definitely get different result of row number assignment every time. If you don't wish to do so - let's say maybe you prefer to order by student/recipe name descending - then you just replace ORDER BY RAND() with ORDER BY Student DESC.
Next we'll make both queries as derived tables then join them by matching the row number like this:
SELECT *
FROM
(SELECT Student,
ROW_NUMBER() OVER (ORDER BY RAND()) AS Rn1
FROM TempStudents) a
LEFT JOIN
(SELECT Recipe,
ROW_NUMBER() OVER (ORDER BY RAND()) AS Rn2
FROM TempRecipes) b
ON a.Rn1=b.Rn2;
The reason I'm doing LEFT JOIN here is to show that there will be some student without recipe assignment. Here's the result:
Student
Rn1
Recipe
Rn2
Ann
1
Cookies
1
Bob
2
Jello
2
Charlie
3
Pie
3
Daphne
4
Brownies
4
Earl
5
Popsicles
5
Francine
6
Cake
6
George
7
Ice Cream
7
Heather
8
NULL
NULL
Ivan
9
NULL
NULL
Janet
10
NULL
NULL
If you're doing INNER JOIN then you'll not see the last 3 of the result above since they're no matching row number from the recipe table. Our last step is just adding insert command to the query like so:
INSERT INTO TempAssignments
SELECT Recipe, Student
FROM
....
Do note that this example is using random ordering therefore the result in the TempAssignments table after the insert might not be the same as the one you get while doing testing.
Here's a fiddle for reference

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

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.

sql table design to fetch records with multiple inclusion and exclusion conditions

We want to select customers based on following parameters i.e. customer should be in:
specific city i.e. cityId=1,2,3...
specific customerId should be excluded i.e. customerId=33,2323,34534...
specific age i.e. 5 years, 7 years, 72 years...
This inclusion & exclusion list can be any long.
How should we design database for this:
Create separate table 'customerInclusionCities' for these inclusion cities and do like:
select * from customers where cityId in (select cityId from customerInclusionCities)
Some we do for age, create table 'customerEligibleAge' with all entries of eligible age entries:
i.e. select * from customers where age in (select age from customerEligibleAge)
and Create separate table 'customerIdToBeExcluded' for excluding customers:
i.e. select * from customers where customerId not in (select customerId from customerIdToBeExcluded)
OR
Create One table with Category and Ids.
i.e. Category1 for cities, Category2 for CustomerIds to be excluded.
Which approach is better, creating one table for these parameters OR creating separate tables for each list i.e. age, customerId, city?
IN ( SELECT ... ) can be very slow. Do your query as a single SELECT without subqueries. I assume all 3 columns are in the same table? (If not, that adds complexity.) The WHERE clause will probably have 3 IN ( constants ) clauses:
SELECT ...
FROM tbl
WHERE cityId IN (1,2,3...)
AND customerId NOT IN (33,2323,34534...)
AND age IN (5, 7, 72)
Have (at least):
INDEX(cityId),
INDEX(age)
(Negated things are unlikely to be able to use an index.)
The query will use one of the indexes; having both will give the Optimizer a choice of which it thinks is better.
Or...
SELECT c.*
FROM customers AS c
JOIN cityEligible AS b ON b.city = c.city
JOIN customerEligibleAge AS ce ON c.age = ce.age
LEFT JOIN customerIdToBeExcluded AS ex ON c.customerId = ex.customerId
WHERE ex.customerId IS NULL
Suggested indexes (probably as PRIMARY KEY):
customers: (city)
customerEligibleAge: (age)
customerIdToBeExcluded: (customerId)
In order to discuss further, please provide SHOW CREATE TABLE for each table and EXPLAIN SELECT ... for any of the queries actually work.
If you use the database only that operation, I recommend to use the first solution. Also the first solution is very simple to deploy.
The second solution fills up with junk the DB.

Delete from a table matching one criteria where there are rows in same table matching different criteria?

Sorry for the mega title... I was trying to be descriptive enough. I've got a table that contains event attendance data that has some erroneous data in it. The table definition is kind of like this:
id (row id)
date
company_name
attendees
It ended up with some cases where for a given date, there are two entries matching a company_name and date but one has attendees=0 and the other has attendees>0. In those cases, I want to discard the ones where attendees=0.
I know you can't join on the same table while deleting, so please consider this query to be pseudocode that shows what I want to accomplish.
DELETE FROM attendance a WHERE a.attendees=0 AND a.date IN (SELECT b.date FROM attendance b WHERE b.attendees > 0 AND b.company_name = a.company_name);
I also tried to populate a temporary table with the ids of the rows I want to delete, but that query hangs because of the IN (SELECT ...) clause. My table has thousands of rows so that just maxes out the CPU and then times out.
This ugly thing should work (using alias permit to avoid the You can't specify target table for update in FROM clause error)
DELETE FROM attendance
WHERE (attendees, date, company_name)
IN (SELECT c.a, c.d, c.c
FROM
(SELECT MIN(attendees) a, date d, company_name c
FROM attendance
GROUP BY date, company_name
HAVING COUNT(*) > 1) as c);
SqlFiddle

Access Query IIf Value in Values returned by another Query

I have a Query that looks up an Employee ID, Employee Name and Team Name from the current Staff Database. I have another Query that lists all unique values in Team Name. (Team Name is an Integer, and corresponds to Employee ID, also an Integer.)
Eg this is how teams are structured. Emp ID 100 belongs to Emp ID 10's team. 10 belongs to 5. 5 belongs to 1, etc.
EmpID = 100
TeamName = 10
EmpID = 10
TeamName = 5
EmpID = 5
TeamName = 1
What I am trying to do is return a fourth field, giving an asterisk, "*", when the Emp ID also appears in the Team Name query (Thus meaning they have a team/are a Manager).
I have a DCount that works, but it is slower than I'd like, and will only get slower as the Database grows, but it might serve to explain what I need.
Expr1: IIf(DCount("TeamName","jdbo_MostRecentEmpDataRemain","TeamName = " & [EmpID])>0,"*","")
jdbo_MostRecentEmpDataRemain is a Query that returns all data for staff that are active.
This will return an Asterisk if the EmpID has team members assigned to them, and nothing if they do not.
I'm wondering if this can be applied through queries to make it faster. Please let me know if you have any suggesstions.
I tried this: Expr2: IIf([EmpID] In ([qryListOfTeams].[TeamName]),"a","z")
but that returns lots of z's and then an a, as it seems to return a value for every value that is in the Team List. I want it to aggregate those and display an a if there is an a, otherwise a z. (Where in the original "*" is the a, and "" is the z)
Kind regards,
Jamie Warburton
How about:
SELECT * FROM TableOrQuery
LEFT JOIN (SELECT DISTINCT EmpID FROM Teams
WHERE EmpID In (SELECT TeamName FROM Teams)) As a
ON TableOrQuery.EmpID = a.EmpID
Derived tables are usually faster than subqueries.
EDIT re comment
Asterisk has a specific meaning, so while you can do this, I do not recommend it.
SELECT TableOrQuery.*, a.IsTeam FROM TableOrQuery
LEFT JOIN (SELECT DISTINCT EmpID, "*" As IsTeam FROM Teams
WHERE EmpID In (SELECT TeamName FROM Teams)) As a
ON TableOrQuery.EmpID = a.EmpID