Months calculations inside stored procedure - mysql

I need to get a list of months that are not located inside the database.
Example:
Table members
ID | Member's code | Member since
1 | 555-12 | 2012-11-22
Table membership
ID | Code | Paid
1 | 555-12 | 2013-1-1
2 | 555-12 | 2013-3-12
3 | 555-12 | 2013-5-1
Let's say that today is : 2013-11-17
I need to get output like this:
Member's code | Debt ( Months )
555-12 | 11-2012
555-12 | 12-2012
555-12 | 2-2013
555-12 | 4-2013
Is this possible to do with a SQL? Do I need to have a stored procedure where I will pass Member's code?

My idea is to use a number table, that contains just numbers from 0 to 100 or more:
CREATE TABLE numbers (
n INT
);
INSERT INTO numbers (n)
VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)...;
Then you can use a query like this:
SELECT
m.ID,
m.Code,
DATE_FORMAT(m.Member_since + INTERVAL num.n MONTH, '%m-%Y') As Debt_Month_Year
FROM
members m INNER JOIN numbers num
ON TIMESTAMPDIFF(MONTH, m.Member_since, LAST_DAY(CURDATE()))>=num.n
LEFT JOIN membership ms
ON
m.Code = ms.Code
AND
LAST_DAY(ms.Paid)=LAST_DAY(m.Member_since + INTERVAL num.n MONTH)
WHERE
ms.id IS NULL
-- and if you wish, add the following line:
AND m.Code = '555-12'
Please see fiddle here.

select code,
left(since,7) as debt
from user where code='555-12'
union all
select code,
left(date_add(paid, interval -1 MONTH),7) as debt
from paid where code='555-12'
Fiddle
http://sqlfiddle.com/#!2/3d988/1

If you can guarantee that you will only have a consistent one month gap between the entries for each user, then I think timus's solution should work.
If not, I think you'll need to have some sort of date table to determine what values you don't have in the database.
Here's a SQL Fiddle demonstrating what I'm talking about. I intentionally removed one of the rows from your paid table to test skipping more than one month. Basically it gets the month from the member table (the first entry, I guess), and I union that to a second query getting the months that are not in the paid table.
SQL Fiddle
select
left (mbr_since,7) as MNTH
from
user
union
select
mnth
from
months
left outer join paid
on months.mnth = left(paid,7)
where
paid.code is null
The months table is just a row for each month (2013-01, 2013-02, etc). The second query joins to that, and then filters out rows where there is a match in the paid table. Hopefully my explanation is making sense...

Related

Get max price date for a product within a given period (mysql)

My mysql table with prices
+----------+-----+----------+
|product_id|price|date_at |
+----------+-----+----------+
| 1 |41.50|2020-09-01|
| 1 |99.50|2020-09-02|
| 1 |41.50|2020-09-03|
| 1 |41.50|2020-09-04|
| 1 |41.50|2020-09-05|
| 1 |9.90 |2020-09-06|
+----------+-----+----------+
I can get today and yesterday prices:
select today.price,
yesterday.price as yesterday_price
from prices today
left join prices yesterday on today.product_id = yesterday.product_id
where yesterday.date_at = '2020-09-05' <-- I need to have it dynamic
and today.date_at = '2020-09-06'
I need to have yesterday date as any date from period N days with MAX(price).
Example:
yesterday.date_at = last 20 days max price for the same product_id
Maybe I should use more code in left join instead of where.
Please tell me how to change my query.
Apparently what you want to do is something like this:
SELECT MAX(price) FROM prices
WHERE date_at BETWEEN _from_date AND _to_date
AND product_id = _product_id;
You have to hard type the parameters (_from_date; to_date; and _product_id). Should you want to make it dynamic, you will have to think about writing an equivalent stored procedure

mysqli COUNT with WHERE and GROUP BY

I am trying to get the amount of records on specific dates. The problem is that when a date has no records it doesnt show the date.
$asksome= mysqli_query("
SELECT COUNT(worker_id) AS amount, problem.datter AS datum
FROM problem
WHERE worker_id = $idmed
GROUP BY datter"
);
The structure looks like this:
worker_id | datter | note
1 |2018-02-25 |
1 |2018-02-26 |
3 |2018-02-25 |
This is the query I am using. I want the query to show the dates even if the COUNT is 0. what i mean by this is i want the result to be like this:
count result | date
0 | 2018-02-24
2 | 2018-02-25
1 | 2018-02-26
0 | 2018-02-27
- - For people finding this post not knowing what to do: i fixed my issue by making another table that simply contained the specific dates that i want to check.
I used the following query:
SELECT weeks.week_id AS datum, COUNT(problem.datter) AS amount
FROM weeks
LEFT JOIN problem ON problem.datter = weeks.week_id
GROUP BY weeks.week_id
You should make clear for the sql of how to handle the count when it is 0. In general, when using SQL's SUM() and COUNT() along with the GROUP BY statement, 0 is never output. And that is because, the GROUP BY clause has nothing to group with when no records for your count of 0 exist.
For that you can use the COALESCE expression.
You can see its documentation here: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/coalesce-transact-sql
So in your example, the sql query should be:
SELECT COALESCE (SELECT COUNT(worker_id) AS amount, problem.datter AS datum
FROM problem
WHERE worker_id = $idmed
GROUP BY datter"), 0);

Grouping by date and people

I am developing a database in php/mysql.
I have a table ‘matterjuncactions’ which contains the fields
actiondate
howlong
staffid
When a member of staff records an action it is entered into the table with the field howlong recording time as a decimal.
A member of staff could record any number of actions in a day. (There are currently 12 staff members)
What I would like to do is have a page showing a table with dates down the left hand side and staff ids across the top with each cell containing the sum of the time spent for that day. (i.e. sum of’howlong’)
So something like:
Date | Staffid 1 | Staff id2 |
6th August | 3.5 | 2.7 |
5th August | 5.7 | 4.6 |
etc
I can get the totals for a single staff member using:
SELECT DATE_FORMAT(matterjuncactions.actiondate,'%W-%D') AS fDt
, SUM(howlong) AS tottime
FROM matterjuncactions
WHERE staffid=1
GROUP
BY matterjuncactions.actiondate
ORDER
BY matterjuncactions.actiondate DESC
I can’t work out how to get this to display all of the data for all of the staffids.
Try this one
SELECT DATE_FORMAT(matterjuncactions.actiondate,'%W-%D') AS fDt,
( select SUM(howlong) from matterjuncactions AS tottime1 where staffid=1) as total1,
( select SUM(howlong) from matterjuncactions AS tottime2 where staffid=2) as total2
FROM matterjuncactions
GROUP
BY matterjuncactions.actiondate
ORDER
BY matterjuncactions.actiondate DESC
Hope it helps :)

Mixing HAVING with CASE OR Analytic functions in MySQL (PartitionQualify(?

I have a SELECT query that returns some fields like this:
Date | Campaign_Name | Type | Count_People
Oct | Cats | 1 | 500
Oct | Cats | 2 | 50
Oct | Dogs | 1 | 80
Oct | Dogs | 2 | 50
The query uses aggregation and I only want to include results where when Type = 1 then ensure that the corresponding Count_People is greater than 99.
Using the example table, I'd like to have two rows returned: Cats. Where Dogs is type 1 it's excluded because it's below 100, in this case where Dogs = 2 should be excluded also.
Put another way, if type = 1 is less than 100 then remove all records of the corresponding campaign name.
I started out trying this:
HAVING CASE WHEN type = 1 THEN COUNT(DISTINCT Count_People) > 99 END
I used Teradata earlier int he year and remember working on a query that used an analytic function "Qualify PartitionBy". I suspect something along those lines is what I need? I need to base the exclusion on aggregation before the query is run?
How would I do this in MySQL? Am I making sense?
Now that I understand the question, I think your best bet will be a subquery to determine which date/campaign combinations of a type=1 have a count_people greater than 99.
SELECT
<table>.date,
<table>.campaign_name,
<table>.type,
count(distinct count_people) as count_people
FROM
(
SELECT
date,
campaign_name
FROM
<table>
WHERE type=1
HAVING count(distinct count_people) > 99
GROUP BY 1,2
) type1
LEFT OUTER JOIN <table> ON
type1.campaign_name = <table>.campaign_name AND
type1.date = <table>.date
WHERE <table>.type IN (1,2)
GROUP BY 1,2,3
The subquery here only returns campaign/date combinations when both the type=1 AND it has greater than 99 count_people. It uses a LEFT JOIN back to the to insure that only those campaign/date combinations make it into the result set.
The WHERE on the main query keeps the results to only Types 1 and 2, which you stated was already a filter in place (though not mentioned in the question, it was stated in a comment to a previous answer).
Based on your comments to answer by #JNevill I think you will have no option but to use subselects to pre-filter the record set you are dealing with, as working with HAVING is going to limit you only to the current record being evaluated - there is no way to compare against previous or subsequent records in the set in this manner.
So have a look at something like this:
SELECT
full_data.date AS date,
full_data.campaign_name AS campaign_name,
full_data.type AS type,
COUNT(full_data.people) AS people_count
FROM
(
SELECT
date,
campaign_name,
type,
COUNT(people) AS people_count
FROM table
WHERE type IN (1,2)
GROUP BY date, campaign_name, type
) AS full_data
LEFT JOIN
(
SELECT
date,
campaign_name,
COUNT(people) AS people_count
FROM table
WHERE type = 1
GROUP BY date, campaign_name
HAVING people_count < 100
) AS filter
ON
full_data.date = filter.date
AND full_data.campaign_name = filter.campaign_name
WHERE
filter.date IS NULL
AND filter.campaign_name IS NULL
The first subselect is basically your current query without any attempt at using HAVING to filter out results. The second subselect is used to find all date/campaign name combos which have people_count > 100 and use those as a filter for against the full data set.

count rows where date is equal but separated by name

I think it will be easiest to start with the table I have and the result I am aiming for.
Name | Date
A | 03/01/2012
A | 03/01/2012
B | 02/01/2012
A | 02/01/2012
B | 02/01/2012
A | 02/01/2012
B | 01/01/2012
B | 01/01/2012
A | 01/01/2012
I want the result of my query to be:
Name | 01/01/2012 | 02/01/2012 | 03/01/2012
A | 1 | 2 | 2
B | 2 | 2 | 0
So basically I want to count the number of rows that have the same date, but for each individual name. So a simple group by of dates won't do because it would merge the names together. And then I want to output a table that shows the counts for each individual date using php.
I've seen answers suggest something like this:
SELECT
NAME,
SUM(CASE WHEN GRADE = 1 THEN 1 ELSE 0 END) AS GRADE1,
SUM(CASE WHEN GRADE = 2 THEN 1 ELSE 0 END) AS GRADE2,
SUM(CASE WHEN GRADE = 3 THEN 1 ELSE 0 END) AS GRADE3
FROM Rodzaj
GROUP BY NAME
so I imagine there would be a way for me to tweak that but I was wondering if there is another way, or is that the most efficient?
I was perhaps thinking if the while loop were to output just one specific name and date each time along with the count, so the first result would be A,01/01/2012,1 then the next A,02/01/2012,2 - A,03/01/2012,3 - B,01/01/2012,2 etc. then perhaps that would be doable through a different technique but not sure if something like that is possible and if it would be efficient.
So I'm basically looking to see if anyone has any ideas that are a bit outside the box for this and how they would compare.
I hope I explained everything well enough and thanks in advance for any help.
You have to include two columns in your GROUP BY:
SELECT name, COUNT(*) AS count
FROM your_table
GROUP BY name, date
This will get the counts of each name -> date combination in row-format. Since you also wanted to include a 0 count if the name didn't have any rows on a certain date, you can use:
SELECT a.name,
b.date,
COUNT(c.name) AS date_count
FROM (SELECT DISTINCT name FROM your_table) a
CROSS JOIN (SELECT DISTINCT date FROM your_table) b
LEFT JOIN your_table c ON a.name = c.name AND
b.date = c.date
GROUP BY a.name,
b.date
SQLFiddle Demo
You're asking for a "pivot". Basically, it is what it is. The real problem with a pivot is that the column names must adapt to the data, which is impossible to do with SQL alone.
Here's how you do it:
SELECT
Name,
SUM(`Date` = '01/01/2012') AS `01/01/2012`,
SUM(`Date` = '02/01/2012') AS `02/01/2012`,
SUM(`Date` = '03/01/2012') AS `03/01/2012`
FROM mytable
GROUP BY Name
Note the cool way you can SUM() a condition in mysql, becasue in mysql true is 1 and false is 0, so summing a condition is equivalent to counting the number of times it's true.
It is not more efficient to use an inner group by first.
Just in case anyone is interested in what was the best method:
Zane's second suggestion was the slowest, I loaded in a third of the data I did for the other two and it took quite a while. Perhaps on smaller tables it would be more efficient, and although I am not working with a huge table roughly 28,000 rows was enough to create significant lag, with the between clause dropping the result to about 4000 rows.
Bohemian's answer gave me the least amount to code, I threw in a loop to create all the case statements and it worked with relative ease. The benefit of this method was the simplicity, besides creating the loop for the cases, the results come in without the need for any php tricks, just simple foreach to get all the columns. Recommended for those not confident with php.
However, I found Zane's first suggestion the quickest performing and despite the need for extra php coding it seems I will be sticking with this method. The disadvantage of this method is that it only gives the dates that actually have data, so creating a table with all the dates becomes a bit more complicated. What I did was create a variable that keeps track of what date it is supposed to be compared to the table column which is reset on each table row, when the result of the query is equal to that date it echoes the value otherwise it does a while loop echoing table cells with 0 until the dates do match. It also had to do a check to see if the 'Name' value is still the same and if not it would switch to the next row after filling in any missing cells with 0 to the end of that row. If anyone is interested in seeing the code you can message me.
Results of the two methods over 3 months of data (a column for each day so roughly 90 case statements) ~ 12,000 rows out of 28,000:Bohemian's Pivot - ~0.158s (highest seen ~0.36s)Zane's Double Group by - ~0.086s (highest seen ~0.15s)