MySQL - Chaining multiple SQL commands - mysql

I've looked upon multiple threads but can't seem to find a desirable answer to my question. I am creating a system with a scheduler in it and I need multiple chains in order for the query to return an answer. So here is the scenario. A user needs to register and upon registration, the user is presented with a date.
I have users table where users(obviously) are listed. One column here has the date.
There is also a date table where the dates are stored. Each date can only occupy 30 persons.
The date table also has the availability column. If the date is available, it is labeled 1. If the date has expired (the current date is higher than this date), it is labeled 0.
for example, i have dates Jan 1, Jan 2 and Jan 3 and the current date is Jan 2. Obviously, Jan 1 should be expired. That wouldn't be included in the list so I will set the availability to 0 (yes, manually). There is only Jan 2 and Jan 3. I also need to find if Jan 2 has accommodated 30 people. Else, I need to put him to Jan 3. I got a bunch of parts of the codes that I don't know how to chain.
Expected Output :
query1 (Jan 2 and Jan 3 should be the result)
SELECT * FROM rooms WHERE availability = 0
RoomID Room Date Room Availability
1 Jan 1 1
2 Jan 2 0
3 Jan 3 0
query2 - (count people assigned in specific rooms)
SELECT COUNT(RoomAssigned) FROM users
Users RoomAssigned
Jack 2
Eddie 2
query3 - (system should be able to locate the first room that is available)
if (query2 results<30)
put new user to rooms from result in query1
If ever the chaining I was looking for would possibly be not advisable, I am open for different suggestions. Thanks. :)

Your question seems to be very confused about what tables you have and what's in them, and I don't see how your sample queries can produce the output you showed. But it seems like this query will do what you want:
SELECT *
FROM rooms
WHERE availability = 1
AND roomID NOT IN (
SELECT roomAssigned
FROM users
WHERE availability = 1
GROUP BY roomAssigned
HAVING COUNT(*) >= 30)
ORDER BY roomDate
LIMIT 1
The subquery finds all the rooms that are filled, and then we exclude those from the main query. Then we sort the remaining rooms by date, and select the first one with LIMIT 1.

I think you're going to want something like this:
SELECT r.[RoomID],
r.[Room Date]
FROM rooms r
LEFT JOIN users u
ON r.[RoomID] = u.[RoomAssigned]
WHERE r.[Room Date] >= CURDATE()
GROUP BY r.[RoomId], r.[Room Date]
ORDER BY r.[Room Date], r.[Room Id]
HAVING COUNT(u.[RoomAssigned]) < 30
LIMIT 1
I haven't tested it, so it may require some tweaking. It's like #Barmar's answer, only using a join instead of a nested select. I also checked the availability based on the current date, not the availability column, which I don't think should be stored in the database, because it can be determined based on the Room Date.

Related

MySQL: Aggregate weekly statistics with substatics (subqueries)

I would like to gather weekly statics on a MySQL-Table.
The table itself has the following structure:
user_id action_id created
0 123 2017-01-01 00.00:00
0 124 ...
1 123 ...
... ... ...
I would like to aggregate the weekly statics for:
How many user where active per week
This is rather simple:
SELECT
YEARWEEK(created) as week,
COUNT(DISTINCT user_id) AS count
FROM data
GROUP BY YEARWEEK(created);
Additionally I could apply a sorting.
The result looks like:
week count
201701 2
201702 3
How many user where active per week for the very first time
I thought about solving it by using a subquery
SELECT
YEARWEEK(created) as week,
COUNT(DISTINCT user_id) AS count,
(
SELECT
COUNT(DISTINCT d2.user_id)
FROM data d2
WHERE YEARWEEK(d2.created) = week
AND NOT EXISTS (SELECT 1 FROM data d3
WHERE YEARWEEK(d3.created) < week AND d2.user_id = d3.user_id)
) as countNewUsers
FROM data d1
GROUP BY YEARWEEK(created);
How many junior user where active per week
Junior users were active between 1 and 10 times before the related week
Similar to the one above, but with other subquery
How many power user where active per week
Senior users were active more than 10 times before the related week
This works as expected, but has a rather poor performance, since the subquery is evaluated before the grouping happens. With millions of rows in a table, this takes ages.
Does anybody have a better solution for this query, ideally returning all values in single result set?
I think all of your queries could derive from one 'intermediate' table. It would contain (yearweek, userid, count).
Users active per week: Pretty much the same query, but faster from this table.
Active for first time: Self-join ON userid and desired week versus MIN(yearweek)
Uses before the target week: ... SUM(count) WHERE ... < week GROUP BY userid
Use the above to determine which userids of Junior/Power.

Mysql in-time query

I'm in need of some help structuring in-time queries. There's a few of them I need - but I think that if I can be shown how to do one, I can figure out the others.
What I'm after:
-Rolling 12 month view of 'inactive accounts'...ie number of accounts that have not placed an order in the 12 months prior.
-This ideally will be a subquery (in a much larger script) joining back on to a dates table (see below)
January 2015 | # of customers with no orders from 1/2014-1/2015
February 2015 | # of customers with no orders from 2/2014-2/2015
March 2015 | # of customers with no orders from 3/2014-3/2015
etc...
What I'm having trouble wrapping my mind around is how I'd structure a where clause to ensure that it scans all orders and only returns the total of account ID's that had not placed an order in the year prior to that month. I've used different combinations of DATEDIFF, DATESUB etc.
SELECT DATE_FORMAT(order_datetime, '%Y-%m'), COUNT DISTINCT (account_id)
FROM warehouse.orders
JOIN warehouse.accounts ON xyz
WHERE...
It feels like I'm on the right path - I just keep mentally going in circles trying to figure this out.
Cheers and thanks in advance.
I don't have enough reputation points to simply comment on your question. I don't fully understand it though.
Are you using SQLServer/TSQL or MySQL?
Do you want to have just one column which calculates the last 12 months' rolling average or 12 columns for the rolling average each month? If it is just one figures for the last 12 months tolling do you want that to be from the current day or the beginning of that month?
If it was SQL Server and a rolling 12 months to now, the calculation could be:
SELECT SUM(CASE WHEN DATEDIFF(y,GETDATE(),order_date_time) < 1 THEN COUNT(DISTINCT account_id) END) as January2015
If you're using MySQL replace GETDATE() with NOW()
If you want one value rolling but to the beginning of the month then you could use:
SELECT SUM(CASE WHEN DATEDIFF(y,DATEADD(M, DATEDIFF(M, 0, GETDATE()), 0),order_date_time) < 1 THEN COUNT(DISTINCT account_id) END) as January2015
If I've missed the point entirely, please let me know and I'll happily amend the answer
You should query between dates, in order to get the count of events for each id.
select case
when count(account_id)<0 then 'INACTIVE'
when count(account_id)>0 then 'ACTIVE'
from warehouse.orders
where data_format(order_datetime, '%m/%Y') between '1/2014' and '1/2015'
group by account_id)

Find two entries in a database table with different criteria

I have a membership mySQL database. One of the tables is for membership history and records each time a member renews. There is a unique auto-increment field, one for the member's login name (which is the constant for each member) and one for expiry date.
I'm looking for a way to search for all members who, for instance, expired in Nov 2014 and then renewed in Jan 2015 (their new expiry date would be Jan 2016). The data for each one would be in two separate rows in the table.
So I'm looking to find the login_name where expiry = Nov 14 and also find the same login name if there's another entry of expiry = Jan 16 then display the result as a list.
I've been working along these lines but I'm not there yet...
SELECT * FROM `membership_history`
WHERE `login_name` IN (
SELECT `login_name`
FROM `membership_history`
WHERE `membership_enddate` BETWEEN '2014-11-01' AND '2014-11-30'
OR `membership_enddate` BETWEEN '2016-01-01' AND '2016-01-31'
GROUP BY `login_name` HAVING count(`login_name`) > 1
)
ORDER BY `login_name`
Any tips or help would be appreciated - thanks
First, I'd be inclined to write this as a join rather than in. Then, you can eliminate the where clause, unless needed for performance reasons (it filters the data but is not needed to get the same results):
SELECT mh.*
FROM `membership_history` mh JOIN
(select login_name
from membership_history mh2
group by login_name
having sum(membership_enddate BETWEEN '2014-11-01' AND '2014-11-30') > 0 and
sum(membership_enddate BETWEEN '2016-01-01' AND '2016-01-31') > 0
) ml
on mh.login_name = ml.login_name;

Number of rows returned when adding before/after date logically inconsistant

I have a database containing information about a students visit to our tutoring center. Each time a student visits a record is produced which includes their names, their student number the date they visited, what they were there for and how long they were there.
We create new tables for each term
I was asked to get an unduplicated count of how many students were there during a certain term so I run the following.
SELECT * FROM `tutoringdata_201350` group by `anum`
anum being the students unique identification number, which returns 524 results out of 5525 total records. In theory that should be my unduplicated count.
I was then asked to get records before and after a certain date in that same table, so I run.
SELECT * FROM `tutoringdata_201350` WHERE `cDate` <= "09/30/2013" group by `anum`
Which works, so far as the date is concerned and no duplicate people are returned so far as I can see in the results window if I sort by the anumber they are all unique. BUT the total number of results returned is 375
So to get students after that date I run
SELECT * FROM `tutoringdata_201350` WHERE `cDate` > "09/30/2013" group by `anum`
which also appears to work, no duplicated students in the returned results but total number of returned results is 428.
375 + 428 is 803 not 524 which I would expect. I'm having trouble following the logic, which of the 2 different types of queries are producing an inaccurate number of results.
You are misusing the pernicious nonstandard MySQL extension to GROUP BY. http://dev.mysql.com/doc/refman/5.6/en/group-by-handling.html
You probably want something like this;
SELECT COUNT(DISTINCT anum) unique_students
FROM `tutoringdata_201350`
WHERE cDate <= 'whatever'
You could also do this to find out how many visits each student made.
SELECT COUNT(*) visits,
anum
FROM `tutoringdata_201350`
WHERE cDate <= 'whatever'
GROUP BY anum
You are also, I think, making a mistake in the way you are comparing dates.
MySQL uses an internal DATE and DATETIME format. If you want to compare such data items in your table to a text-string constant, you need to use the correct format -- YYYY-MM-DD -- for that string. For example:
WHERE cDate <= '2013-09-30'
The comparison in your example isn't correct. Edit. If your dates are stored as text strings as MM/DD/YYYY, you need to use the following sort of comparison.
WHERE STR_TO_DATE(cDate, '%m/%d/%Y') <= '2013-09-30'
This will convert your legacy date strings to DATE format. Then the comparison will work. If you don't do this MySQL is just comparing strings to strings. (You may, or may not, luck out if the years don't vary.)
Now, your counts of unique students before and after the first of October do not necessarily sum up to the count of unique students for the whole term. Here's an example.
Joe Sept 28
Joe Sept 29
Mary Sept 30
Henry Sept 30
Joe Oct 1
Stephen Oct 1
Overall there are four distinct students. In Sept there are three. In October there are two. If you add those two numbers you get five. That's more because you're double-counting Joe by adding those two numbers.

Get stats for each day in a month without ignoring days with no data

I want to get stats for each day in a given month. However, if a day has no rows in the table, it doesn't show up in the results. How can I include days with no data, and show all days until the current date?
This is the query I have now:
SELECT DATE_FORMAT(FROM_UNIXTIME(timestamp), '%d'), COUNT(*)
FROM data
WHERE EXTRACT(MONTH FROM FROM_UNIXTIME(timestamp)) = 6
GROUP BY EXTRACT(DAY FROM FROM_UNIXTIME(timestamp))
So if I have
Row 1 | 01-06
Row 2 | 02-06
Row 3 | 03-06
Row 4 | 05-06
Row 5 | 05-06
(i changed timestamp values to a day/month date just to explain)
It should output
01 | 1
02 | 1
03 | 1
04 | 0
05 | 2
06 | 0
...Instead of ignoring day 4 and today (day 6).
You will need a calendar table to do something in the form
SELECT `date`, count(*)
FROM Input_Calendar c
LEFT JOIN Data d on c.date=d.date
GROUP BY `date`
I keep a full copy of a calendar table in my database and used a WHILE loop to fill it but you can populate one on the fly for use based on the different solutions out there like http://crazycoders.net/2012/03/using-a-calendar-table-in-mysql/
In MySQL, you can use MySQL variables (act like in-line programming values). You set and can manipulate as needed.
select
dayofmonth( DynamicCalendar.CalendarDay ) as `Day`,
count(*) as Entries
from
( select
#startDate := date_add( #startDate, interval 1 day ) CalendarDay
from
( select #startDate := '2013-05-31' ) sqlvars,
AnyTableThatHasAsManyDaysYouExpectToReport
limit
6 ) DynamicCalendar
LEFT JOIN Input_Calendar c
on DynamicCalendar.CalendarDay = date( from_unixtime( c.date ))
group by
DynamicCalendar.CalendarDay
In the above sample, the inner query can join against as the name implies "Any Table" in your database that has at least X number of records you are trying to generate for... in this case, you are dealing with only the current month of June and only need 6 records worth... But if you wanted to do an entire year, just make sure the "Any Table" has 365 records(or more).
The inner query will start by setting the "#startDate" to the day BEFORE June 1st (May 31). Then, by just having the other table, will result in every record joined to this variable (creates a simulated for/next loop) via a limit of 6 records (days you are generating the report for). So now, as the records are being queried, the Start Date keeps adding 1 day... first record results in June 1st, next record June 2nd, etc.
So now, you have a simulated calendar with 6 records dated from June 1 to June 6. Take that and join to your "data" table and you are already qualifying your dates via the join and get only those dates of activity. I'm joining on the DATE() of the from unix time since you care about anything that happend on June 1, and June 1 # 12:00:00AM is different than June 1 # 8:45am, so matching on the date only portion, they should remain in proper grouping.
You could expand this answer by changing the inner '2013-05-31' to some MySQL Date function to get the last day of the prior month, and the limit based on whatever day in the current month you are doing so these are not hard-coded.
Create a Time dimension. This is a standard OLAP reporting trick. You don't need a cube in order to do OLAP tricks, though. Simply find a script on the internet to generate a Calendar table and join to that table.
Also, I think your query is missing a WHERE clause.
Other useful tricks include creating a "Tally" table that is a list of numbers from 1 to N where N is usually the max of the bigint on your database management system.
No code provided here, as I am not a MySQL guru.
Pseudo-code is:
Select * from Data left join TimeDimension on data.date = timedimension.date