MySQL - SQL select query with two tables using where, count and having - mysql

There are two tables: client and contract.
client table:
client_code INT pk
status VARCHAR
A client can have 1 or more contracts. The client has a status column which specifies if it has valid contracts - the values are 'active' or 'inactive'. The contract is specified for a client with active status.
contract table:
contract_code INT pk
client_code INT pk
end_date DATE
A contract has an end date. A contract end date before today is an expired contract.
REQUIREMENT: A report requires all active clients with contracts, but with all (not some) contracts having expired date. Some example data is shown below:
Client data:
client_code status
----------------------------------
1 active
2 inactive
3 active
4 active
Contract data:
contract_code client_code end_date
-------------------------------------------------------------
11 1 08-12-2018
12 1 09-12-2018
13 1 10-12-2018
31 3 11-31-2018
32 3 10-30-2018
41 4 01-31-2019
42 4 12-31-2018
Expected result:
client_code
-------------
1
RESULT: This client (client_code = 1) has all contracts with expired dates: 08-12-2018, 09-12-2018 and 10-12-2018.
I need some help to write a SQL query to get this result. I am not sure what constructs I have to use - one can point out what I can try. The database is MySQL 5.5.

One approach uses aggregation. We can join together the client and contract tables, then aggregate by client, checking that, for an active client, there exist no contract end dates which occur in the future.
SELECT
c.client_code
FROM client c
INNER JOIN contract co
ON c.client_code = co.client_code
WHERE
c.status = 'active'
GROUP BY
c.client_code
HAVING
SUM(CASE WHEN co.end_date > CURDATE() THEN 1 ELSE 0 END) = 0;
Demo
Note: I am assuming that your dates are appearing in M-D-Y format simply due to the particular formatting, and that end_date is actually a proper date column. If instead you are storing your dates as text, then we might have to make a call to STR_TO_DATE to convert them to dates first.

Is that what you're looking for?
select clients.client_code
from clients
join contracts
on contracts.client_code=clients.client_code
where status='active'
group by clients.client_code
having min(end_date)>curdate()

Related

SQL select with dynamic count of "BETWEEN" conditions based on joined table

I want to add a messenger to my pet project, but I am having difficulty writing database queries. I use MySQL for this service with Hibernate as ORM. Almost all queries was written in HQL, but in principle I can use native queries.
Messenger can contain group conversations. In addition to writing messages, user can enter the conversation, leave it, clear personal message history. User sees all messages when he has been in a conversation, but he can also clear the history and see only messages after the last clearing.
Below I described the simplified structure of two tables important for this task.
Message table:
ID
text
timestamp
1
first_msg
1609459200
2
second_msg
1609545600
Member_event table:
id
user_id
type
timestamp
1
1
1
1609459100
2
1
3
1609459300
3
1
2
1609459400
4
1
1
1609545500
where type:
1 - user entered the chat,
2 - user leaved the chat,
3 - user cleared his own history of messages in the chat
Is it possible to read all chat messages available to the user with one request?
I have no idea how to check conditions dynamically: WHERE message's timestamps are between all "entered-leaved" cycles and after the last "entered" if not leaved BUT only after the last history clearing. If exists.
I think you could proceed with these steps:
take the union of both tables and consider the records in order of time stamp
Use window functions to determine whether the most recent 1 or 2 type was a 1. We can use a running sum where type 1 adds one and type 2 subtracts one (and 3 does nothing to it). With another window function you could determine whether there is still a type 3 following. The combination of these two informations can be translated to a 1 when the line belongs to an interval that must be collected, and a 0 when not.
Filter the previous result to just get the message records, and only those where the calculation was 1.
Here is the query:
with unified as (
select id, text, timestamp, null as type
from message
union
select id, null, timestamp, type
from member_event
where user_id = 1),
validated as (
select unified.*,
sum(case type when 1 then 1 when 2 then -1 else 0 end)
over (order by timestamp
rows unbounded preceding) *
min(case type when 3 then 0 else 1 end)
over (order by timestamp
rows between current row and unbounded following) valid
from unified
order by timestamp)
select id, text, timestamp
from validated
where type is null and valid = 1
order by timestamp
I do not see, how you could match the Member_event table to the Message_table without an additional FOREIGN_KEY. Are you trying to assign the Messages available to the User via Timestamp?
If so try this:
SELECT * FROM MESSAGE_TABLE m
WHERE m.TIMESTAMP BETWEEN
(SELECT TOP 1 TIMESTAMP FROM MEMBER_EVENT_TABLE WHERE type = 1 ORDER BY TIMESTAMP DESC)
AND (SELECT TOP 1 TIMESTAMP FROM MEMBER_EVENT_TABLE WHERE type != 1 ORDER BY TIMESTAMP DESC)
This at least should show the last Messages between join and clean/leave

Getting a syntax error when trying to join two tables

I have created the following tables:
USER TABLE
user_id (primary key)
account_created (date)
email (varchar)
usage_count (number)
PRODUCT TABLE
product_id (primary key)
product (varchar) (values include “iPhone”, “Android”, “Windows”)
users_supported (number) (users supported notes: some phones can support group calls up to 1000 users, some can only support normal calls of 2 users)
USAGE TABLE
usage_id (primary key)
product_id (foreign key)
user_id (foreign key)
usage_date (date)
purchase_call (number) (can be a 0, 2, 4, 6, or 10 min call)
usage_winnings (number) (when users use their minutes, sometimes they will randomly earn cash back)
computer_usage (binary value) (users can link the phone to a computer, and make calls through their computer, similar to google voice)
I want to write a select statement with the following constraints:
Time frame between 2014 and 2016
% of calls made for 2 users
% of purchased minutes used for only 2 users
Only in the first 30 days after a user created their account
In each year between 2014 and 2016, what percentage of calls and purchased calls were for only 2 users in each user's first 30 days after they created their account.
I have been practicing joins and what I have is:
SELECT COUNT(p.users_supported = 2)/COUNT(p.users_supported), SUM(CASE WHEN users_supported = 2 THEN us.purchase_call ELSE 0 END)/SUM(CASE WHEN users_supported <> 2 THEN us.purchase_call ELSE 0 END)
FROM USERS u
JOIN USAGE us ON u.user_id = us.user_id
JOIN PRODUCT p ON p.product_id = us.product_id
WHERE u.account_created >= '2014-01-01'
AND u.account_created <= '2016-12-31'
AND u.account_created <= u.account_created + 30
I have several errors right now - the percentages are not coming out correct and the account created with 30 days constraint is causing an error that breaks the whole query. Any suggestions would be much appreciated!
You did not state what database you are using...
First thing I notice is that
AND u.account_created <= u.account_created + 30
is always true. I think you want the query based on the current date like
AND u.account_created > NOW() - 30
If you are using sql server then you could use the datediff function and check for a result < 30
Since it was pointed out that you tagged this question mysql then using TIMESTAMPDIFF will work. Here is an example showing the syntax and the use of the NOW() function.
mysql> select TIMESTAMPDIFF(day,NOW(),'20161206');
+-------------------------------------+
| TIMESTAMPDIFF(day,NOW(),'20161206') |
+-------------------------------------+
| -17 |
+-------------------------------------+
1 row in set (0.04 sec)

mysql highly selective query

I have a data set like this:
User Date Status
Eric 1/1/2015 4
Eric 2/1/2015 2
Eric 3/1/2015 4
Mike 1/1/2015 4
Mike 2/1/2015 4
Mike 3/1/2015 2
I'm trying to write a query in which I will retrieve users whose MOST RECENT transaction status is a 4. If it's not a 4 I don't want to see that user in the results. This dataset could have 2 potential results, one for Eric and one for Mike. However, Mike's most recent transaction was not a 4, therefore:
The return result would be:
User Date Status
Eric 3/1/2015 4
As this record is the only record for Eric that has a 4 as his latest transaction date.
Here's what I've tried so far:
SELECT
user, MAX(date) as dates, status
FROM
orders
GROUP BY
status,
user
This would get me to a unqiue record for every user for every status type. This would be a subquery, and the parent query would look like:
SELECT
user, dates, status
WHERE
status = 4
GROUP BY
user
However, this is clearly flawed as I don't want status = 4 records IF their most recent record is not a 4. I only want status = 4 when the latest date is a 4. Any thoughts?
SELECT user, date
, actualOrders.status
FROM (
SELECT user, MAX(date) as date
FROM orders
GROUP BY user) AS lastOrderDates
INNER JOIN orders AS actualOrders USING (user, date)
WHERE actualOrders.status = 4
;
-- Since USING is being used, there is not a need to specify source of the
-- user and date fields in the SELECT clause; however, if an ON clause was
-- used instead, either table could be used as the source of those fields.
Also, you may want to rethink the field names used if it is not too late and user and date are both found here.
SELECT user, date, status FROM
(
SELECT user, MAX(date) as date, status FROM orders GROUP BY user
)
WHERE status = 4
The easiest way is to include your order table a second time in a subquery in your from clause in order to retrieve the last date for each user. Then you can add a where clause to match the most recent date per user, and finally filter on the status.
select orders.*
from orders,
(
select ord_user, max(ord_date) ord_date
from orders
group by ord_user
) latestdate
where orders.ord_status = 4
and orders.ord_user = latestdate.ord_user
and orders.ord_date = latestdate.ord_date
Another option is to use the over partition clause:
Oracle SQL query: Retrieve latest values per group based on time
Regards,

Multiple distinct counts with where

I am having an issue creating most efficient query for multiple distinct counts of a column with different where clauses. My MYSQL table looks like this:
id client_id result timestamp
---------------------------------------------------
1 1234566 escalated 2014-01-02 00:00:00
2 1233344 approved 2014-02-03 00:00:00
3 1234566 escalated 2014-01-02 01:00:00
What I am trying to achieve is to build the following data in the return:
Total number of unique client IDs processed from the beginning of time.
Total number of unique client IDs processed escalated from the beginning of time.
Total number of unique client IDs processed approved from the beginning of time.
Count of unique client IDs approved within specified timeframe using between statement on timestamp.
Count of unique client IDs escalated within specified timeframe using between statement on timestamp.
I have thought about running multiple selects, but I think it would be a waste of resources, and possibly if this could be done with a single query it would the best way to handle it, unfortunately my experience is lacking in this area. What I would like would the return to simple contain an alias and the count.
Any help would be appreciated.
You want conditional aggregation, something like:
select count(distinct ClientId) as NumClients,
count(distinct case when result = 'Approved' then ClientId end) as NumApproved,
count(distinct case when result = 'Escalated' then ClientId end) as NumEscalated,
count(distinct case when result = 'Approved' and timestamp between #Time1 and #Time2
then ClientId end) as NumApproved,
count(distinct case when result = 'Escalated' and timestamp between #Time1 and #Time2
then ClientId end) as NumEscalated,
from table t;

How can I find the correct prior status row in this table with a SQL query?

Imagine a workflow for data entry. Some forms come in, they are typed into a system, reviewed, and hopefully approved. However, they can be rejected by a manager and will have to be entered again.
So, an ideal workflow would go like this:
recieved > entered > approved
But this COULD happen:
received > entered > rejected > entered > rejected > approved
At each stage, we record who updated the form to its current status - who entered it, who rejected it, or who approved it. So the forms status table looks like this:
form_id status updated_by updated_at
1 received Bob (timestamp)
1 entered Bob (timestamp)
1 approved Susan (timestamp)
2 received Bob (timestamp)
2 entered Bob (timestamp)
2 rejected Susan (timestamp)
2 entered Carla (timestamp)
2 rejected Susan (timestamp)
2 entered Sam (timestamp)
2 approved Susan (timestamp)
Here's what I'm trying to do: write a rejection report. I want a row for each rejection, and joined to that row, I want to see who did the work that got rejected.
As a human, I can see that, for a given status row with status 'rejected', the row that will tell me who did the faulty work will be the one that
shares the same form_id and
has a prior timestamp closest to the rejection.
But I'm having trouble telling MySQL that.
Can anybody see how to construct this query?
A subselect ended up working for me.
SELECT
`s1`.`form_id`,
(
SELECT
`s2`.`updated_by`
FROM
statuses s2
WHERE
`s2`.`form_id` = `s1`.`form_id`
AND
`s2`.`updated_at` < `s1`.`updated_at`
ORDER BY
`s2`.`updated_at` DESC
LIMIT 1
) AS 'made_rejected_change'
FROM
statuses s1
WHERE
`s1`.`status` = 'rejected'
Another solution that uses subselect (this time not a correlated subquery):
SELECT
w1.*,
w2.entered_by
FROM (
SELECT
wr.form_id,
wr.updated_at AS rejected_at,
wr.updated_by AS rejected_by,
MAX(we.updated_at) AS entered at
FROM workflow wr
INNER JOIN workflow we ON we.status = 'entered'
AND wr.form_id = we.form_id
AND wr.updated_at > we.updated_at
WHERE wr.status = 'rejected'
GROUP BY
wr.form_id,
wr.updated_at,
wr.updated_by
) w1
INNER JOIN workflow w2 ON w1.form_id = w2.form_id
AND w1.entered_at = w2.updated_at
The subselect lists all the rejecters and the immediately preceding entered timestamps. Then the table is joined once again to extract the names corresponding to the entered_at timestamps.
You want to get the rejected timestamp and then figure out the entry that appeared right before it based on the timestamp. I'm assuming that timestamp actually holds a date/time and isn't an SQL server timestamp field (completely different).
declare #rejectedTimestamp timestamp
select #rejectedTimestamp = timestamp
from table
where status = 'rejected'
select top 1 *
from table
where timestamp < #rejectedtimestamp
order by timestamp desc