SUM data from other table when joining some tables with conditions - php

I got a complicated question.
I have a query that combine data from several tables. for example:
traffic: id, traffic_source_id, first_hit_date
goals: id, goal_type, goal_date, goal_value
traffic_sources: id, source_name
goal_type could be "contact form", "demo download", "purchase"
goal_value is numeric. and display the number of times the user reach the goal on the same date (day).
I'm trying to show one row for each traffic_id, and then sum every goal for this traffic_id. When a user comes for the first time, he/she gets a traffic id that stays with him (cookie). he/she can reach a goal a week later after the first hit date, and then other goal 2 days later, for example.
I want to be able to query from date > to date and show the correct sum values of the goals for that speific range.
When I try to use SUM(CASE WHEN ...) I can't sum just the range withing the goals table.
example:
traffic
from: 1-feb to 28-feb
3 | google adwords | 0 contact | 1 demo download | 1 purchase
4 | facebook | 1 contact | 3 demo download | 3 purchase
but when I want to change the range from 1-feb to 14-feb
3 | google adwords | 0 contact | 1 demo download | 0 purchase
4 | facebook | 0 contact | 2 demo download | 2 purchase
hope I'm clear enough...
any advice will be much appreciated.
Update:
query example of what I have now:
SELECT traffic.traffic_id as original_traffic_id hit_date, referrer, referrer_url, keyword, ip,
SUM(CASE goals.goal_type WHEN 'Contact' THEN 1 ELSE 0 END) goal_contact,
SUM(CASE goals.goal_type WHEN 'Download' THEN 1 ELSE 0 END) goal_download,
SUM(CASE goals.goal_type WHEN 'Signup' THEN 1 ELSE 0 END) goal_signup,
SUM(CASE goals.goal_type WHEN 'Purchase' THEN 1 ELSE 0 END) goal_purchase
FROM traffic
LEFT JOIN goals ON goals.traffic_id = traffic.traffic_id
WHERE traffic.traffic_id=100 AND hit_date >= '$from_date' AND hit_date <= '$to_date'
(where $from_date and $to_date are mysql date formats)
(this is not the real query since the original query much larger and includes about 7 more tables that joins in)
This query actually sums all the goals without having into cound goal_date. I want to be able to limit the SUM to the range of $from_date and $to_date
Hope it clears it a bit more.

I don't have the answer for this yet, but I what I did is to summerize all goals with another query prior to the first one.
The array looks like $goals_array[traffic_id][goal_type] -> value
And while looping through the main sql, just fetch the value from the $goals_array.
It works fast, and altough I wish to make this on one query, that's ok for now.

Related

Create view from live stats

I created a sistem to input results from a school basketball tournament. The idea is that after the game the operators will input the result in a format that the system fetches to save in the db in a format like the one below:
Date | Team | Score 1Q | Score 2Q | Score 3Q | Score 4Q | Score OT | Final Score | W | L | Won over Team | Lost to Team | Regular Season? | Finals?
I created a PHP page that calculate many stats from the table above, like Total Wins, Win%, Avg Points, Avg. Points per Quarter, % Turn Around Games when loosing on Half Time or 3Q, % Finals games disputed, Times became champions etc, and many more deep stats.
But I was thinking in creating a View with this information calcalated on the DB and in real time, instead of having the script handles it.
But how can I turn the selects needed from the first table into a working second table with all calculations done whenever we make the selection?
Thanks
#decio, I think your idea about creating a view to calculate those stats is not a bad idea. You might be able to do so with the something similar to the following SQL script:
CREATE VIEW result_stats_view AS SELECT SUM(W) as total_wins, SUM(L) as total_losses FROM precalculate_stats_table_name;
This shows the total wins and losses for the season, but you probably get the idea. Check out MySQL aggregate functions (like average, sum, etc.) here:
https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html
Once you have your calculations added to the view then you can simply do query like this to get your calculated data:
SELECT * from result_stats_view

Get next and previous records sorting by non-unique field

I was not able to find an answer while reading similar questions, so I'll ask my question here, appreciate any help on this matter.
In MySQL DB have a table with articles:
id date is_active title text
3 2017-01-20 1 New payment system goes live some articl
5 2017-01-21 1 Library v.2.5 released some articl
6 2017-01-22 1 New skins and themes some articl
7 2017-01-25 0 Terms and Conditions updated some articl
8 2017-01-26 1 Don't forget to subscribe some articl
10 2017-01-22 0 Support Chat beta release some articl
11 2017-01-30 1 Maintenance window next Sunday some articl
12 2017-01-28 1 Refer a friend and get a bonus some articl
13 2017-01-26 1 Follow us in social networks some articl
14 2017-01-22 1 Video sharing feature is now live some articl
I have 2 web pages:
a list of all active articles ordered by date (important: it is possible that several articles can have same date).
This works fine, no issue here.
single article read page, additionally I have "Next" and "Previous" links on that page
This is an issue.
I would like to show 'next' and 'prev' links as a href="article.php?id=8". So I would like to get next record ID from DB according to the query like this:
SELECT id FROM articles WHERE date > (SELECT date FROM news WHERE id = 6) and isactive = 1 ORDER BY date ASC LIMIT 1;
However my query does not work properly when it comes to defining next article id with the same date. When user stays on the article id=3 and clicks 'next' multiple times I would expect the following order of loading articles:
id title date is_active
5 Library v.2 2017-01-21 1
6 New skins a 2017-01-22 1
14 Video shari 2017-01-22 1
8 Don't forge 2017-01-26 1
13 Follow us i 2017-01-26 1
12 Refer a fri 2017-01-28 1
11 Maintenance 2017-01-30 1
But what I get is: 5, 6, 8, 12, 11. So 2 records are missing in this sequence (ids: 14 and 13) because they have same date.
I tried playing with different queries, but all results are not 100% right (in some cases it keeps returning same numbers, ex.: 5, 6, 14, 6, 14....., etc. )
Is is possible to get the 'next' IDs based on current ID and desired order via MySQL query(ies)? I am not against doing several queries or working with nested queries.
As a workaround, of course I can just retrieve an ordered array of all ids like this:
SELECT id FROM articles WHERE isactive=1 ORDER BY date
and then define 'next' and 'previous' using this array, however I don't like this solution.
If you can point me to some similar topics or other possible ways to solve this, please do.
Thank you.
SELECT id
FROM articles
WHERE date >= (SELECT date -- changed
FROM news
WHERE id = 6)
AND isactive = 1
AND id NOT IN(6) -- added, maybe id!=6 or id<>6
ORDER BY date ASC
LIMIT 1
-- (all id date >= date) - (id=6)
Why you do this, why not use
SELECT id FROM news ORDER BY date ASC LIMIT 6,1 -- possition, count
If SQL query caching, this is may be faster.

Sum user working time in a given MySQL table

The following table has the time when the user enters/quit on the company:
ID | user_id | day_unix | time
10 1 1459220400 1459293745
9 1 1459220400 1459293711
8 1 1459220400 1459293689
7 1 1459220400 1459293678
6 1 1459220400 1459293669
11 1 1459220400 1459293761
day_unix consists in the first second of the day, this way, GROUP BY can be easily used in future.
time consists in the time that the user click's on the button to start/stop working. Time also can be changed to a native DATETIME column.
I want to create a SQL query capable of summing the time between the entries. So the query must to jump "odd" entries that can be considered as the beginnig of a small coffee break and right after the "odd" entries the user has started working again, so the query must to sum the working time, excluding the coffee breaks.
Any idea? Here is what I've already got:
SELECT * FROM table GROUP BY day_unix ORDER BY time ASC

mysql - getting only the results with diferences from same table

So I have a single table inside which I have a score system for points. It looks something along this line:
Columns:
ID Name Date Points
1 Peter 2014-07-15 5
2 John 2014-07-15 6
3 Bill 2014-07-15 3
and so on...
Everyday, the new results are being put into the table with the total amount of points acumulated, however in order to be able to get historic values, the results are put into new rows. So on the 2014-07-16, the table will look like this:
ID Name Date Points
1 Peter 2014-07-15 5
2 John 2014-07-15 6
3 Bill 2014-07-15 3
4 Peter 2014-07-16 11
5 John 2014-07-16 12
6 Bill 2014-07-16 3
However sometimes when a player doesn't take part for the whole day and doesn't get any points, he will still be added, but the points will remain the same (here this is shown by the case of Bill).
My question is how to count the number of each type of players (active - Peter and John ie when the points value changes from one date to another and inactive - Bill ie when the points value stays the same).
I have managed to get this query to only select players who do have the same value, but it's giving me the list of players rather than the count. Although I could potentialy be wrong with this query:
SELECT Points, name, COUNT(*)
FROM points
WHERE DATE(Date) = '2014-07-15' OR DATE(Date) = '2014-07-16'
GROUP BY Points
HAVING COUNT(*)>1
I'm not sure how to count the number of rows (could do a bypass trick with PHP getting the number of rows, but interested in SQL only) or how to invert it, to get a count of players who have a different score (again, could get total of rows and then subtract the above number, but not interested in that either - I'd prefer the SQL).
Regards and thanks in advance.
You are pretty close.
If you have at most one row per "player" per "date", you could do something like this:
SELECT SUM(IF(c.cnt_distinct_points<2,1,0)) AS cnt_inactive
, SUM(IF(c.cnt_distinct_points>1,1,0)) AS cnt_active
FROM ( SELECT p.name
, COUNT(DISTINCT p.points) AS cnt_distinct_points
FROM points p
WHERE DATE(p.Date) IN ('2014-07-15','2014-07-16')
GROUP BY p.name
) c
The inline view query (aliased as c) gets a count of the distinct number of "points" values for each player. We need to "group by" name, so we can get a distinct list of players, along with an indication whether the points value was different or not. If all of the non-NULL "points" values for a given player are the same, COUNT(DISTINCT ) will return a value of 1. Otherwise, we'll get a value larger than 1.
The outer query processes that list, collapsing all of the rows into a single row. The "trick" is to use expressions in the SELECT list that return 1 or 0, depending on whether the player is "inactive", and perform a SUM aggregate on that. Do the same thing, but a different expression to return a 1 if the player is "active".
If the count of distinct points for a player is 1, we'll essentially be adding 1 to cnt_inactive. Similarly, of the distinct points for a player is greater than 1, we'll be adding 1 to the cnt_active.
If this doesn't make sense, let me know if you have questions.
NOTE: Ideally, we'd avoid using the DATE() function around the p.Date column reference, so we could enable an appropriate index.
If the Date column is defined as (MySQL datatype) DATE, then the DATE() function is unnecessary. If the Date column is defined as (MySQL datatype) DATETIME or TIMESTAMP, we could use an equivalent predicate:
WHERE p.Date >= '2014-07-15' AND p.Date < '2014-07-16' + INTERVAL 1 DAY
That looks more complicated, but a predicate of that form is sargable (i.e. MySQL can use an index range scan to satisfy it, rather than having to look at every row in the table.)
For performance, we'd probably benefit from an index with leading columns of name and date
... ON points (`name`,`date`)
(MySQL may be able to avoid a "Using filesort" operation for the GROUP BY).
I would solve this problem by looking at the previous number of points and then doing a comparison:
select date(date), count(*) as NumActives;
from (select p.*,
(select p2.points
from points p2
where p2.name = p.name and p2.date < p.date
order by p2.date desc
limit 1
) as prev_points
from points p
) p
where prev_points is NULL or prev_points <> points;
Of course, you can add a where clause to get the count for any particular day.

Count number of occurences of a value in different columns in *one* row/record

I might be doing this the wrong way when I set up the tables?
I'm using mysql & php. Loads of googleing only shows how to count how many times a value appears in several rows, I want check for a value in many different columns but in the same record. (I think this: count number of columns that have data for each row just might be about the same thing, maybe? but I don't get it.)
I have a table with goals that I hope to work on and achieve every day. So for every day I wish to mark: "success" or "fail". And insert is working great. How ever I'm looking for a way to calculate the number of success& number of fails of "today", to show the right kind of smiley which will be encouraging or sad depending on number of fails and number of successes.
For example:
ID date drinkMoreWater goToBedEarlier callADearFriend
1 2012 jan 15 fail fail fail
1 2012 jan 16 success _(still empty) success
So if today is jan 15 the smiley will be very very sad.
If today is jan 16 the smiley will be really really hppy with stars in it's eye's (atleast until I fail goal 2 ;) )
Your tables should actually be structured differently to make your life easier:
Goals:
ID Goal
1 drinkMoreWater
2 goToBedEarly
3 callADearFriend
Status:
ID Status
1 Success
2 Fail
Tracking:
ID Date Goal_ID Status_ID
1 1/1/2012 1 1
1 1/1/2012 3 2
1 1/2/2012 2 1
1 1/2/2012 4 1
Now you could easily add goals and status (e.g. 'Working on it') and your table structure does not have to change to accommodate your changes, your queries become a lot simpler as well.
If you want to do that in SQL, you could use a ternary-like construct, like that:
SELECT *,
(CASE drinkMoreWater WHEN 'success' THEN 1 ELSE 0 END)
+ (CASE goToBedEarlier WHEN 'success' THEN 1 ELSE 0 END)
+ (CASE callADearFriend WHEN 'success' THEN 1 ELSE 0 END)
AS numberOfSuccesses
FROM yourTable
You can do something like this:
Select case when sub.smilies = 0 then "very very sad" else
case when sub.smilies = 1 then "sad" else "hubby" end
end as "Number of Smilies"
from
(
Select case when t.drinkMoreWater = "fail" then 0 else 1 end +
case when t.goToBedEarlier = "fail" then 0 else 1 end +
case when t.callADearFriend = "fail" then 0 else 1 end as smilies
from yourTableName t
where date = #date
) sub
You will need to handle the empty strings, and work around these case statements.
This solution is for your current design but you better off consider the redesign suggested by #BassamMehanni 's answer

Categories