How can I apply mathematical function to MySQL query? - php

I've got the following query to determine how many votes a story has received:
SELECT s_id, s_title, s_time, (s_time-now()) AS s_timediff,
(
(SELECT COUNT(*) FROM s_ups WHERE stories.q_id=s_ups.s_id) -
(SELECT COUNT(*) FROM s_downs WHERE stories.s_id=s_downs.s_id)
) AS votes
FROM stories
I'd like to apply the following mathematical function to it for upcoming stories (I think it's what reddit uses) -
http://redflavor.com/reddit.cf.algorithm.png
I can perform the function on the application side (which I'm doing now), but I can't sort it by the ranking which the function provides.
Any advise?

Try this:
SELECT s_id, s_title, log10(Z) + (Y * s_timediff)/45000 AS redditfunction
FROM (
SELECT stories.s_id, stories.s_title, stories.s_time,
stories.s_time - now() AS s_timediff,
count(s_ups.s_id) - count(s_downs.s_id) as X,
if(X>0,1,if(x<0,-1,0)) as Y,
if(abs(x)>=1,abs(x),1) as Z
FROM stories
LEFT JOIN s_ups ON stories.q_id=s_ups.s_id
LEFT JOIN s_downs ON stories.s_id=s_downs.s_id
GROUP BY stories.s_id
) as derived_table1
You might need to check this statement if it works with your datasets.

y and z are the tricky ones. You want a specific return based on x's value. That sounds like a good reason to make a function.
http://dev.mysql.com/doc/refman/5.0/en/if-statement.html
You should make 1 function for y and one for z. pass in x, and expect a number back out.
DELIMINATOR //
CREATE FUNCTION y_element(x INT)
RETURNS INT
BEGIN
DECLARE y INT;
IF x > 0 SET y = 1;
ELSEIF x = 0 SET y = 0;
ELSEIF x < 0 SET y = -1;
END IF;
RETURN y;
END //;
DELIMINATOR;
There is y. I did it by hand without checking so you may have to fix a few typo's.
Do z the same way, and then you have all of the values for your final function.

Related

Finding available rooms from datespan

I have a start date and a end date.
$s="YYYY-MM-DD";
$e="YYYY-MM-DD";
I need to make a query that checks the available rooms for that span.
cObjects , cBookings_details
cObjects_id = cBookings_details_cObjects_id
Im really stuck here, would really appreciate some help.
My Current Query
SELECT cObjects.*
FROM cObjects LEFT JOIN cBookings_details ON cObjects.cObjects_id = cBookings_details.cBookings_details_cObjects_id
WHERE cBookings_details.cBookings_details_arrival NOT BETWEEN '".$s."' AND '".$e."'
AND cBookings_details.cBookings_details_departure NOT BETWEEN '".$s."' AND '".$e."'
AND cBookings_details.cBookings_details_arrival <> '".$s."' AND cBookings_details_departure <> '".$s."'
AND cBookings_details.cBookings_details_arrival <> '".$e."' AND cBookings_details_departure <> '".$e."'
AND cObjects.cObjects_type < '2'
OR cBookings_details.cBookings_details_id is null AND cObjects_type < '2'
This is a common problem.. actually just wrote a guide for this at work:
Selecting rows with start and end dates that feature in a chosen interval
-------------------------------------------------------------------------
Chosen Interval - :start_date (S)
:end_date (E)
Rows - row.start_date (s)
row.end_date (e)
Increasing date-->
S E
| | Chosen Interval
1. s---e Rows we want
2. s---e .
3. s---e .
4. s----------e .
5. s--e Rows we don't want
6. s--e .
LOGIC:
row.end_date >= :start_date AND row.start_date <= :end_date
If you are looking for available rooms, LEFT JOIN the room to the bookings using the logic above and SELECT rows where the booking is NULL.
Applied to your schema I believe this would come out as:
SELECT o.*
FROM cObjects o
LEFT JOIN cBookings_details bd
ON bd.cBookings_details_cObjects_id = o.cObjects_id
AND bd.cBookings_details_arrival <= :end_date
AND bd.cBookings_details_departure >= :start_date
WHERE bd.cBookings_details_id IS NULL
(AND o.cObjects_type < '2') # Double check this is what you want
By the way, I would cry if I had to work with your schema. Why repeat the table name in the column? It just adds noise.

Display dynamic ranges from a database table and count the rows within each range

I have database table like this:
I want to display different 5-year age ranges and the counts of students that are in that range like below:
Here, the lowest age is 10 so we first calculate the range 10-15. There are 5 students within that range. For the second range, we need to find the age>15 which is 18. So, the second range is from 18-23, and so on. I would appreciate any help where the range is automatically calculated and count the data within that range.
You can use a condition inside of a SUM() statement to get a count where that condition holds. I would count the conditions where the age is BETWEEN() the necessary range. Try this:
SELECT
SUM(age BETWEEN 10 AND 15) AS '10-15',
SUM(age BETWEEN 18 AND 23) AS '18-23',
SUM(age BETWEEN 26 AND 31) AS '26-31',
SUM(age BETWEEN 34 AND 39) AS '34-39'
FROM myTable;
This will only return one row, but it will have everything you need. Here is an SQL Fiddle example.
EDIT I misunderstood your question to automatically calculate the various ranges. I will leave my previous answer here because it may be beneficial to future readers looking for hard coded ranges. To do this, you'll have to set up a variable. I made a sort of running total type approach to get the groups. I started by setting #a to 0 before the query. Then, I needed to get two values:
The minimum age from the table where age > #a
5 greater than that variable.
I did this by changing the value of #a as necessary:
#a := (SELECT MIN(age) FROM myTable WHERE age >= #a)
#a := #a + 5
Then, I included these in a CONCAT() block and casted these values as chars in order to get the groups that I needed. It may look complicated, so I hope I explained the concept:
SELECT CONCAT
(CAST(#a := (SELECT MIN(age) FROM myTable WHERE age > #a) AS CHAR),
' - ',
CAST((#a := #a + 5) AS CHAR)) AS ageRange
FROM myTable
WHERE #a <= (SELECT MAX(age) FROM myTable);
Doing this gave me four rows, each with the age ranges you expect. I had to add the where clause because otherwise I would get one result row for each row in the table, which would give us several null rows.
Last, I included a subquery to get the count of students whose age is within the necessary range. Note that the first part changes the values of #a, so instead of checking from #a to #a + 5, I check from #a-5 to #a. Here is the final query:
SET #a = 0;
SELECT CONCAT(CAST(#a := (SELECT MIN(age) FROM myTable WHERE age > #a) AS CHAR), ' - ', CAST((#a := #a + 5) AS CHAR)) AS ageRange,
(SELECT COUNT(*) FROM myTable WHERE age BETWEEN #a - 5 AND #a) AS numStudents
FROM myTable
WHERE #a <= (SELECT MAX(age) FROM myTable)
GROUP BY ageRange;
It worked beautifully in SQL Fiddle. Completely dynamic and returns the various groups of 5 without any prior knowledge of which groups to take.
SELECT
CASE
WHEN age>=10 AND age<=15 THEN '10-15'
WHEN age>=18 AND age<=23 THEN '18-23'
WHEN age>=26 AND age<=31 THEN '26-31'
WHEN age>=34 AND age<=39 THEN '34-39'
ELSE 'OTHER'
END
AS age_range,
COUNT(*) as number_of_students
FROM table
GROUP BY age_range

sql Runtime reports

I have a query which displays information from a table like this.
SELECT tag.label, inputs.input_raw, inputs.unix_timestamp, tag.inverted FROM inputs
JOIN tag
ON tag.tag_id = inputs.tag_id
WHERE (inputs.tag_id = 92084)
AND (inputs.date_time > dateadd(day,-1,getdate()))
ORDER BY date_time DESC
I would like to write a query which would do two things.
I need a count of every time input_raw switches from '0' to '1'.
I also need a total time of the pump running, using the unix_timestamp ie. when the input_raw = 1.
Does anyone have any ideas.
I would settle for an algorithm to use php to get the results I need but I've hit a brick wall and haven't been able to figure it out.
Thanks
EDIT: The table also contains a date_time field which matches the value of the unix_timestamp if there is a date_time method that can be used>
You want to use the Lead orLag function to compare the current result with either the previous or next. These functions, however, are introduced in SQL Server 2012.
With the help of Mr. pinaldave I managed to produce the following SQL Fiddle that counts every change from 0 to 1.
;WITH x AS
(
SELECT
1 AS ldOffset, -- equiv to 2nd param of LEAD
1 AS lgOffset, -- equiv to 2nd param of LAG
NULL AS ldDefVal, -- equiv to 3rd param of LEAD
NULL AS lgDefVal, -- equiv to 3rd param of LAG
ROW_NUMBER() OVER (ORDER BY unix_timestamp) AS row,
label,
input_raw,
unix_timestamp,
inverted
FROM inputs
)
SELECT
COUNT(1)
FROM x
LEFT OUTER JOIN x AS xLg
ON x.row = xLg.row + x.lgOffset
WHERE xLg.input_raw = 0 AND x.input_raw = 1;
You can use the same Lag function to calculate the difference between the current timestamp and the previous timestamp.
EDIT:
This SQL Fiddle should show how to get the total time the pump is running (you'll have to define running yourself. This query now assumes that going from 0 to 1 or staying 1 is running. You should also double check my timestamp calculations cause I've never used unix timestamps before.
;WITH x AS
(
SELECT
1 AS ldOffset, -- equiv to 2nd param of LEAD
1 AS lgOffset, -- equiv to 2nd param of LAG
NULL AS ldDefVal, -- equiv to 3rd param of LEAD
NULL AS lgDefVal, -- equiv to 3rd param of LAG
ROW_NUMBER() OVER (ORDER BY unix_timestamp) AS row,
label,
input_raw,
unix_timestamp,
inverted
FROM inputs
)
SELECT
SUM(DATEDIFF(mi,
DATEADD(ss, xLg.unix_timestamp,'01/01/1970'),
DATEADD(ss, x.unix_timestamp,'01/01/1970')))
FROM x
LEFT OUTER JOIN x AS xLg
ON x.row = xLg.row + x.lgOffset
WHERE
(xLg.input_raw = 0 AND x.input_raw = 1)
OR
(xLg.input_raw = 1 AND x.input_raw = 1);
EDIT 2:
I guess the easiest way to check for inverted is the change the WHEREclause to something like:
WHERE
(x.inverted = 1 AND xLg.input_raw = 0 AND x.input_raw = 1)
OR
(x.inverted = 0 AND xLg.input_raw = 1 AND x.input_raw = 0)

SELECT from two tables WHERE different columns in each table equal $id ORDER BY common column (PHP/MySQL)

I'm trying to SELECT from two tables and ORDER BY date (a column they both have). One table (tableA) has a column called "A" and the other table (tableB) has a column called "B", I use array_key_exists() to differentiate between the two (If "A" key exists, I run the array through FunctionA(), if "B" key exists, I run the array through FunctionB()). I only need the 20 latest (date wise) entries. I need the SQL Query to accomplish this.
I already know a reply will be "if they're similarly structured, then you should just use a single table", but I don't want to do that because tableA is drastically different from tableB (a lot more columns in tableA), and using a single table to store the data would result in a LOT of empty columns for entries formatted for tableB, not to mention it'd be a very ugly looking table format due to tableB not needing the majority of tableA's columns).
I just want to display data from both tables in an ordered (by date) fashion, and in one single stream.
I need to SELECT WHERE tableA.poster_id = $id and tableB.receiver_id = $id by the way.
SOLUTION:
I'm updating this just in case anyone else with the same dilemma comes along. After implementing the SQL query that #Erik A. Brandstadmoen had graciously given me, this is basically what my code ended up as:
$MySQL->SQL("SELECT * FROM
(SELECT A.id AS id, A.date AS date, 'tableA' AS source
FROM tableA A WHERE A.poster_id = $id
UNION
SELECT B.id AS id, B.date AS date, 'tableB' AS source
FROM tableB B WHERE B.receiver_id = $id) AS T
ORDER BY T.date DESC LIMIT 0, 20");
$GetStream = array();
$i = 0;
while ($row = mysql_fetch_array($MySQL->Result))
{
$GetStream[$i]['id'] = $row['id'];
$GetStream[$i]['date']=$row['date'];
$GetStream[$i]['source'] = $row['source'];
$i++;
}
*** later on down the code ***
$i = 0;
while ($i<count($GetStream))
{
if ($GetStream[$i]['source'] == "tableA")
{
FunctionA($GetStream[$i]);
}
else
{
FunctionB($GetStream[$i]);
}
$i++;
}
Try using UNION:
SELECT * FROM (
SELECT A.col1 AS x, A.col2 As y, A.col3 AS date FROM tableA A
WHERE tableA.poster_id = $id
UNION
SELECT B.colA AS x, B.colB AS y, B.colC AS date FROM tableB B
WHERE tableB.receiver_id = $id
)
ORDER BY date DESC
LIMIT 0, 20
OR, IF you would like to keep duplicates between tableA and tableB, use UNION ALL instead.
EDIT, according to your comments, I understand that you need a column indicating which table the row is from. You can just add a static column in the select, like this:
SELECT * FROM (
SELECT A.col1 AS x, A.col2 As y, A.col3 AS date, 'A' as source FROM tableA A
WHERE tableA.poster_id = $id
UNION
SELECT B.colA AS x, B.colB AS y, B.colC AS date, 'B' as source FROM tableB B
WHERE tableB.receiver_id = $id
)
ORDER BY date DESC
LIMIT 0, 20
This gives you a nice table on the following form:
x y date source
=========================
(v1) (v2) (d1) 'A'
(v3) (v4) (d2) 'B'
(v1) (v2) (d3) 'B'
(v3) (v4) (d4) 'A'
That does what you want, doesn't it? It's a bit difficult understanding what you are really trying to achieve with this...

MySQL return every nth record from selected range [duplicate]

I have a series of values in a database that I need to pull to create a line chart. Because i dont require high resolution I would like to resample the data by selecting every 5th row from the database.
SELECT *
FROM (
SELECT
#row := #row +1 AS rownum, [column name]
FROM (
SELECT #row :=0) r, [table name]
) ranked
WHERE rownum % [n] = 1
You could try mod 5 to get rows where the ID is multiple of 5. (Assuming you have some sort of ID column that's sequential.)
select * from table where table.id mod 5 = 0;
Since you said you're using MySQL, you can use user variables to create a continuous row numbering. You do have to put that in a derived table (subquery) though.
SET #x := 0;
SELECT *
FROM (SELECT (#x:=#x+1) AS x, mt.* FROM mytable mt ORDER BY RAND()) t
WHERE x MOD 5 = 0;
I added ORDER BY RAND() to get a pseudorandom sampling, instead of allowing every fifth row of the unordered table to be in the sample every time.
An anonymous user tried to edit this to change x MOD 5 = 0 to x MOD 5 = 1. I have changed it back to my original.
For the record, one can use any value between 0 and 4 in that condition, and there's no reason to prefer one value over another.
SET #a = 0;
SELECT * FROM t where (#a := #a + 1) % 2 = 0;
I had been looking for something like this. The answer of Taylor and Bill led me to improve upon their ideas.
table data1 has fields read_date, value
we want to select every 2d record from a query limited by a read_date range
the name of the derived table is arbitrary and here is called DT
query:
SET #row := 0;
SELECT * FROM ( SELECT #row := #row +1 AS rownum, read_date, value FROM data1
WHERE read_date>= 1279771200 AND read_date <= 1281844740 ) as DT WHERE MOD(rownum,2)=0
If you're using MariaDB 10.2, MySQL 8 or later, you can do this more efficiency, and I think more clearly, using common table expressions and window functions.
WITH ordering AS (
SELECT ROW_NUMBER() OVER (ORDER BY name) AS n, example.*
FROM example ORDER BY name
)
SELECT * FROM ordering WHERE MOD(n, 5) = 0;
Conceptually, this creates a temporary table with the contents of the example table ordered by the name field, adds an additional field called n which is the row number, and then fetches only those rows with numbers which are exactly divisible by 5, i.e. every 5th row. In practice, the database engine is often able to optimise this better than that. But even if it doesn't optimise it any further, I think it's clearer than using user variables iteratively as you had to in earlier versions of MySQL.
You can use this query,
set #n=2; <!-- nth row -->
select * from (SELECT t.*,
#rowid := #rowid + 1 AS ID
FROM TABLE t,
(SELECT #rowid := 0) dummy) A where A.ID mod #n = 0;
or you can replace n with your nth value
SELECT *
FROM (
SELECT #row := #row +1 AS rownum, posts.*
FROM (
SELECT #row :=0) r, posts
) ranked
WHERE rownum %3 = 1
where posts is my table.
If you don't require the row number in the result set you can simplify the query.
SELECT
[column name]
FROM
(SELECT #row:=0) temp,
[table name]
WHERE (#row:=#row + 1) % [n] = 1
Replace the following placeholders:
Replace [column name] with a list of columns you need to fetch.
Replace [table name] with the name of your table.
Replace [n] with a number. e.g. if you need every 5th row, replace it with 5

Categories