How to get shortest path between source and destination - mysql

My Mysql table consist as follows
I want to get shortest path from here. I tried 'WITH RECURSIVE' way. but it gives mysql error. Is there any way of doing that? I want to list items which gives shortest path.

You can do something like this, but note that this scales abysmally poorly for larger data sets...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,origin VARCHAR(12) NOT NULL
,destination VARCHAR(12) NOT NULL
,distance INT NOT NULL
,UNIQUE(origin,destination)
);
INSERT INTO my_table VALUES
(1,'MILAN','TURIN',150),
(2,'TURIN','MILAN',150),
(3,'MILAN','VENICE',250),
(4,'VENICE','MILAN',250),
(5,'MILAN','GENOA',200),
(6,'MILAN','ROME',600),
(7,'ROME','MILAN',600),
(8,'MILAN','FLORENCE',380),
(9,'TURIN','GENOA',160),
(10,'GENOA','TURIN',160),
(11,'FLORENCE','VENICE',550),
(12,'FLORENCE','ROME',220),
(13,'ROME','FLORENCE',220);
SELECT x.origin
, y.origin waypoint1
, z.origin waypoint2
, COALESCE(z.destination,y.destination,x.destination) destination
, x.distance+COALESCE(y.distance,0)+COALESCE(z.distance,0) distance
FROM my_table x
LEFT
JOIN my_table y
ON y.origin = x.destination
AND y.destination <> x.origin
LEFT
JOIN my_table z
ON z.origin = y.destination
AND z.destination <> y.origin
AND z.destination <> x.origin
WHERE x.origin = 'ROME'
HAVING destination = 'VENICE'
ORDER
BY distance LIMIT 1;
+--------+-----------+-----------+-------------+----------+
| origin | waypoint1 | waypoint2 | destination | distance |
+--------+-----------+-----------+-------------+----------+
| ROME | MILAN | NULL | VENICE | 850 |
+--------+-----------+-----------+-------------+----------+

Related

Creating specific size

I have a table in html.I want to add that table with php on my database. The problem is with my table. I have done that
CREATE TABLE `playersrb` (
`position` numeric(24) DEFAULT NULL,
`piece_color` enum('B','R') NOT NULL,
`id` numeric(30) DEFAULT NULL,
`last_action` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
)
I have 24 positions and that I want to give in my table that those 24 are that size,not 25 and going on..Second,I have B and R that it is the color (It is ok that).Now I put Id because that's how I name the images I have on my table (the table that it is in my html) ,I name those Id:0 ,Id:1 until 30 (because 30 are the max images I have - I don't want more).
I create with that my table,I open sqlite,I go to that table and I start putting
position piece_color id last_action
Null | R | 0 | here it was saying the time
Null | R | 1 | the time as previous
Null | B | 2 |
Null | B | 3 |
Null | R | 4 |
Null | R | 5 |
Null |B | 6 |
and it goes like this until the end of 30
.
.
.
.
.
I press save the button ,all fine.I go to phpmyadmin to check my table and it wasn't as I created..How can I do that thing?to have position 24 ,30 id (that will be different images ) .Save the 30 Id to 24 position.
edited: as you can see from the image I have created position and id .The table I want is like those. The position is where it belongs my image. The id is the image.I just want to pass from a table I have the position and the id in that table and I want to be right..When I move those images they create a table with variable position and Id. That table I want to pass into my table (in database table).If I put more images I will have Id 2 and the position which I dragged.That's what I am trying to do. As you can understand I want to have only 30 images.Every image is unique. They have other Id,not the same.More details,id =0 is the image a , the id =1 is the image b .The positions is ,in the image as you can see it is just the number of the table you see it where I move those images.
EDITED
<table class="content-table">
<tr>
<th>Image</th>
<th>Position(TO)</th>
<th>New Position</th>
</tr>
</div>
</div>
</div>
<?php
require_once "C/dbconnect.php";
$sql = "SELECT image, position,new_position FROM playersrb" ;
$sql="
SET #old_position = 1;
SET #new_position = 12;
SELECT image
, position old_order
, ROUND(CASE WHEN position NOT BETWEEN LEAST(#old_position,#new_position) AND GREATEST(#old_position,#new_position)
THEN position
WHEN position = #old_position THEN #new_position
ELSE position+(((#new_position<#old_position)-.1)*12)
END
,0) new_order
FROM playersrb;
";
$result = $mysqli-> query($sql);
if($result-> num_rows >0) {
while ($row = $result-> fetch_assoc()){
echo "<tr><td>". $row["image"] ."</td><td>". $row["position"] ."</td></tr>";
}
echo "</table>";
}
else{
echo "0 result";
}
$mysqli->close();
?>
</table>
I don't really understand your question, but here's an example of re-ordering a list...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(image CHAR(1) NOT NULL PRIMARY KEY
,position INT NOT NULL
);
INSERT INTO my_table VALUES
('A',1),
('B',2),
('C',3),
('D',4),
('E',5),
('F',6);
So, let's say we want to drag the image in position 5 to position 2...
SET #old_position = 5;
SET #new_position = 2;
SELECT image
, position old_order
, ROUND(CASE WHEN position NOT BETWEEN LEAST(#old_position,#new_position) AND GREATEST(#old_position,#new_position)
THEN position
WHEN position = #old_position THEN #new_position
ELSE position+(((#new_position<#old_position)-.5)*2)
END
,0) new_order
FROM my_table;
+-------+-----------+-----------+
| image | old_order | new_order |
+-------+-----------+-----------+
| A | 1 | 1 |
| B | 2 | 3 |
| C | 3 | 4 |
| D | 4 | 5 |
| E | 5 | 2 |
| F | 6 | 6 |
+-------+-----------+-----------+
Here's a fuller example, using some PHP to output to HTML... perhaps someone else can make it pretty...
<?php
//simple_sorter.php
//Preamble
/*
A simple row repositioning script.
This is using a simple $_GET to determine which row is repositioned.
So you need to supply a source and a target in the url, e.g.:
https://path/to/simple_sorter.php?old_position=5&new_position=2
There is no error checking, so it can quite easily fall apart, and because
the SELECT comes befpre the UPDATE, you won't see any changes until the
next time you load the page.
*/
//Data Creation Statements
/*
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(image CHAR(1) NOT NULL PRIMARY KEY
,position INT NOT NULL
);
INSERT INTO my_table VALUES
('A',1),
('B',2),
('C',3),
('D',4),
('E',5),
('F',6);
*/
require('path/to/pdo/connection/stateme.nts');
//My understanding is that the following is needed
in order to replace (every instance within the
query of) :old_position and :new_position with
their corresponding values
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
//and now to the code...
$query = "
SELECT *
FROM my_table
ORDER
BY position
";
$stmt = $pdo->prepare($query);
$stmt->execute();
$data = $stmt->fetchAll();
print_r($data);
$query = "
UPDATE my_table x
JOIN
( SELECT image
, position old_order
, ROUND(CASE WHEN position NOT BETWEEN LEAST(:old_position,:new_position) AND GREATEST(:old_position,:new_position)
THEN position
WHEN position = :old_position THEN :new_position
ELSE position+(((:old_position>:new_position)-.5)*2)
END
,0) new_order
FROM my_table
) y
ON y.image = x.image
SET position = new_order
";
$old_position = $_GET['old_position'];
$new_position = $_GET['new_position'];
$stmt = $pdo->prepare($query);
$stmt->execute(array('old_position' => $old_position,'new_position' => $new_position));
?>
Outputs (for instance, and depending what values were used, and how often)...
Array
(
[0] => Array
(
[image] => A
[position] => 1
)
[1] => Array
(
[image] => D
[position] => 2
)
[2] => Array
(
[image] => E
[position] => 3
)
[3] => Array
(
[image] => B
[position] => 4
)
[4] => Array
(
[image] => C
[position] => 5
)
[5] => Array
(
[image] => F
[position] => 6
)
)

Trying to make a pingpong stat tracking database with a stored procedure

I'm using a stored procedure to (try to) write to 3 different tables in MYsql to track ping-pong data and show cool statistics.
So I'm a complete noob to MySQL (and StackOverflow) and haven't really done any sort of database language before so all of this is pretty new to me. I'm trying to make a stored procedure that writes ping-pong stats that come from Ignition(I'm fairly certain that Ignition isn't the problem. It's telling me the writes failed so I think it's a problem with my stored procedure).
I currently have one stored procedure that writes to the players table and can add wins, losses, and total games played when a button is pressed. My problem now is that I want to add statistics where I can track the score and who played against who so I could make graphs and stuff.
This stored procedure is supposed to search through the pingpong table to find if the names passed have played against each other before so I can find the corresponding MatchID. If the players haven't played before, then it should create a new row with a new MatchID(This is the key so it should be unique every time). Once I have the MatchID, I can then figure out how many games the players have played against each other before, what the score was, and who beat who and stuff like that.
Here's what I've written and MySQL says it's fine, but obviously it's not working. I know it's not completely finished but I really need some guidance since this is my second time doing anything with MySQL or and database language for that matter and I don't think this should be failing when I test any sort of write.
CREATE DEFINER=`root`#`localhost` PROCEDURE `Matchups`(
#these are passed from Ignition and should be working
IN L1Name VARCHAR(255), #Player 1 name on the left side
IN L2Name VARCHAR(255), #Player 2 name on the left side
IN R1Name VARCHAR(255), #Player 3 name on the right side
IN R2Name VARCHAR(255), #Player 4 name on the right side
IN TWOvTWO int, #If this is 1, then L1,L2,R1,R2 are playing instead of L1,R1
IN LeftScore int,
IN RightScore int)
BEGIN
DECLARE x int DEFAULT 0;
IF((
SELECT MatchupID
FROM pingpong
WHERE (PlayerL1 = L1Name AND PlayerR1 = R1Name) OR (PlayerL1 = R1Name AND PlayerR1 = L1Name)
)
IS NULL) THEN
INSERT INTO pingpong (PlayerL1, PlayerL2, PlayerR1, PlayerR2) VALUES (L1Name, L2Name, R1Name, R2Name);
INSERT INTO pingponggames (MatchupID, Lscore, Rscore) VALUES ((SELECT MatchupID
FROM pingpong
WHERE (PlayerL1 = L1Name AND PlayerR1 = R1Name) OR (PlayerL1 = R1Name AND PlayerR1 = L1Name)), LeftScore, RightScore);
END IF;
END
Here are what my tables currently look like:
pingpong
PlayerL1 | PlayerL2 | PlayerR1 | PlayerR2 | MatchupID
-----------------------------------------------------
L1 | NULL | R1 | NULL | 1
L1 | NULL | L2 | NULL | 3
L1 | NULL | R2 | NULL | 4
L1 | NULL | test2 | NULL | 5
pingponggames
GameID | MatchupID | LScore | RScore
------------------------------------------
1 | 1 | NULL | NULL
pingpongplayers
Name | TotalWins | TotalLosses | GamesPlayed
-----------------------------------------------------
L1 | 8 | 5 | NULL
L2 | 1 | 1 | NULL
R1 | 1 | 6 | 7
R2 | 1 | 1 | NULL
test2 | 1 | 0 | 1
test1 | 0 | 0 | 0
Explained some features, If needed more I need more info
CREATE DEFINER=`root`#`localhost` PROCEDURE `Matchups`(
#these are passed from Ignition and should be working
IN L1Name VARCHAR(255), #Player 1 name on the left side
IN L2Name VARCHAR(255), #Player 2 name on the left side
IN R1Name VARCHAR(255), #Player 3 name on the right side
IN R2Name VARCHAR(255), #Player 4 name on the right side
-- what will be the INPUT other than 1? It's to notice doubles or singles right? so taking 0 as single & 1 as doubles
IN TWOvTWO INT, #If this is 1, then L1,L2,R1,R2 are playing instead of L1,R1
IN LeftScore INT,
IN RightScore INT)
BEGIN
DECLARE x INT DEFAULT 0; # i guess you are using it in the sp
DECLARE v_matchupid INT; #used int --if data type is different, set as MatchupID column datatype
DECLARE inserted_matchupid INT; -- use data type based on your column MatchupID from pingpong tbl
IF(TWOvTWO=0) THEN -- for singles
#what is the need of this query? to check singles or doubles? Currently it search for only single from what you have written, will change according to that
SELECT MatchupID INTO v_matchupid
FROM pingpong
WHERE L1Name IN (PlayerL1, PlayerR1) AND R1Name IN (PlayerL1, PlayerR1); # avoid using direct name(string) have a master tbl for player name and use its id to compare or use to refer in another tbl
# the if part checks is it new between them and insert in both tbls
IF(v_matchupid IS NULL) THEN
INSERT INTO pingpong (PlayerL1, PlayerR1) VALUES (L1Name, R1Name);
SET inserted_matchupid=LAST_INSERT_ID();
INSERT INTO pingponggames (MatchupID, Lscore, Rscore) VALUES (inserted_matchupid, LeftScore, RightScore);
/*
Once I have the MatchID, I can then figure out how many games the players have played against each other before
A: this will not work for new matchup since matchupid is created now
*/
# so assuming if match found update pingponggames tbl with matched matchupid.. i leave it up to you
ELSE
UPDATE pingponggames SET Lscore=LeftScore, Rscore=RightScore WHERE MatchupID=v_matchupid;-- you can write your own
END IF;
-- for doubles
ELSE # assuming the possibilities of TWOvTWO will be either 0 or 1 if more use "elseif(TWOvTWO=1)" for this block as doubles
SELECT MatchupID INTO v_matchupid
FROM pingpong
# Note: If player name are same it will be difficult so better use a unique id as reference
WHERE L1Name IN (PlayerL1, PlayerL2, PlayerR1, PlayerR2) AND
L2Name IN (PlayerL1, PlayerL2, PlayerR1, PlayerR2) AND
R1Name IN (PlayerL1, PlayerL2, PlayerR1, PlayerR2) AND
R2Name IN (PlayerL1, PlayerL2, PlayerR1, PlayerR2);
IF(v_matchupid IS NULL) THEN
INSERT INTO pingpong (PlayerL1, PlayerL2, PlayerR1, PlayerR2) VALUES (L1Name, L2Name, R1Name, R2Name);
SET inserted_matchupid=LAST_INSERT_ID();
INSERT INTO pingponggames (MatchupID, Lscore, Rscore) VALUES (inserted_matchupid, LeftScore, RightScore);
ELSE
UPDATE pingponggames SET Lscore=LeftScore, Rscore=RightScore WHERE MatchupID=v_matchupid;-- you can write your own
END IF;
END IF;
END

how to select some values on the same column then remaining values as the new column in sql

i have this code
insert into tblPernikahan values(1,"YUNIKA TRIRATNADI A","W","1981-11-29 23:11:10","YOGYAKARTA","2008-09-11 12:09:41"),
(2,"YULIA","W","1976-12-05 19:12:21","SEMARANG","2011-10-23 05:10:45"),
(3,"YOLA AZERTI SARI","W","1983-06-01 03:06:40","MAGELANG","2011-06-08 17:06:28"),
(4,"YETI SULIANA","W","1986-04-18 04:04:50","YOGYAKARTA","2005-01-14 21:01:04"),
(5,"YETI KURNIATI P","W","1976-02-18 21:02:18","MUNTILAN","2008-11-19 23:11:50"),
(6,"ZIAD","P","1981-07-08 17:07:06","YOGYAKARTA","2008-09-11 12:09:41"),
(7,"TUNJUNG ARIWIBOWO","P","1978-10-25 07:10:39","YOGYAKARTA","2011-10-23 05:10:45"),
(8,"SUGIMAN","P","1976-09-28 01:09:38","MUNTILAN","2011-06-08 17:06:28"),
(9,"SIGIT SUTOPO","P","1976-06-22 00:06:50","YOGYAKARTA","2005-01-14 21:01:04"),
(10,"RICKY PERMANADJAYA","P","1989-06-09 07:06:23","MAGELANG","2008-11-19 23:11:50");
i want to spilt the 5 values on the nama column where the gender(jkelamin) is w into the new column as female
then the remaining values where the gender is P into the new column as Male
for the example
this as female column
this as male column
its like
YUNIKA TRIATNADI A is married to ZIAD
YULIA married to TUNJUNG ARIWIBOWO and so on.
how can i do this?
I don't quite get it - is "tglnikah" the date of marriage and the only way to identify who is married to each other? There may be multiple couples with identical marriage dates...
Otherwise you could do something like
SELECT t1.nama, t2.nama, t1.tglnikah FROM tblPernikahan t1, tblPernikahan t2 WHERE t1.jkelamin='W' AND t2.jkelamin='P' AND t1.tglnikah=t2.tglnikah AND t1.nama!=t2.nama;
drop table if exists weddings;
CREATE TABLE weddings
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(30) NOT NULL
,gender CHAR(1) NOT NULL
,dob DATETIME NOT NULL
,birthplace VARCHAR(20) NOT NULL
,wedding_date DATE NOT NULL
);
insert into weddings values
(1,"YUNIKA TRIRATNADI A","W","1981-11-29 23:11:10","YOGYAKARTA","2008-09-11 12:09:41"),
(2,"YULIA","W","1976-12-05 19:12:21","SEMARANG","2011-10-23 05:10:45"),
(3,"YOLA AZERTI SARI","W","1983-06-01 03:06:40","MAGELANG","2011-06-08 17:06:28"),
(4,"YETI SULIANA","W","1986-04-18 04:04:50","YOGYAKARTA","2005-01-14 21:01:04"),
(5,"YETI KURNIATI P","W","1976-02-18 21:02:18","MUNTILAN","2008-11-19 23:11:50"),
(6,"ZIAD","P","1981-07-08 17:07:06","YOGYAKARTA","2008-09-11 12:09:41"),
(7,"TUNJUNG ARIWIBOWO","P","1978-10-25 07:10:39","YOGYAKARTA","2011-10-23 05:10:45"),
(8,"SUGIMAN","P","1976-09-28 01:09:38","MUNTILAN","2011-06-08 17:06:28"),
(9,"SIGIT SUTOPO","P","1976-06-22 00:06:50","YOGYAKARTA","2005-01-14 21:01:04"),
(10,"RICKY PERMANADJAYA","P","1989-06-09 07:06:23","MAGELANG","2008-11-19 23:11:50");
SELECT w.name w
, p.name p
FROM weddings w
JOIN weddings p
ON p.wedding_date = w.wedding_date
AND p.gender = 'p'
WHERE w.gender = 'w';
+---------------------+--------------------+
| w | p |
+---------------------+--------------------+
| YUNIKA TRIRATNADI A | ZIAD |
| YULIA | TUNJUNG ARIWIBOWO |
| YOLA AZERTI SARI | SUGIMAN |
| YETI SULIANA | SIGIT SUTOPO |
| YETI KURNIATI P | RICKY PERMANADJAYA |
+---------------------+--------------------+

Two methods of performing cohort analysis in MySQL using joins

I make a cohort analysis processor. Input parameters: time range and step, condition (initial event) to exctract cohorts, additional condition (retention event) to check after each N hours/days/months. Output parameters: cohort analysis grid, like this:
0h | 16h | 32h | 48h | 64h | 80h | 96h |
cohort #00 15 | 6 | 4 | 1 | 1 | 2 | 2 |
cohort #01 1 | 35 | 8 | 0 | 2 | 0 | 1 |
cohort #02 0 | 3 | 31 | 11 | 5 | 3 | 0 |
cohort #03 0 | 0 | 4 | 27 | 7 | 6 | 2 |
cohort #04 0 | 1 | 1 | 4 | 29 | 4 | 3 |
Basically:
fetch cohorts: unique users who did something 1 in every period from time_begin every time_step.
find how many of them (in each cohort) did something 2 after N seconds, N*2 seconds, N*3, and so on until now.
In short - I have 2 solutions. One works too slow and includes a heavy select with joins for each data step: 1 day, 2 day, 3 day, etc. I want to optimize it by joining result for every data step to cohorts - and it's the second solution. It looks like it works but I'm not sure it's the best way and that it will give the same result even if cohorts will intersect. Please check it out.
Here's the whole story.
I have a table of > 100,000 events, something like this:
#user-id, timestamp, event_name
events_view (uid varchar(64), tm int(11), e varchar(64))
example input row:
"user_sampleid1", 1423836540, "level_end:001:win"
To make a cohort analisys first I extract cohorts: for example, users, who send special event '1st_launch' in 10 hour periods starting from 2015-02-13 and ending with 2015-02-16. All code in this post is simplified and shortened to see the idea.
DROP TABLE IF EXISTS tmp_c;
create temporary table tmp_c (uid varchar(64), tm int(11), c int(11) );
set beg = UNIX_TIMESTAMP('2015-02-13 00:00:00');
set en = UNIX_TIMESTAMP('2015-02-16 00:00:00');
select min(tm) into t_start from events_view ;
select max(tm) into t_end from events_view ;
if beg < t_start then
set beg = t_start;
end if;
if en > t_end then
set en = t_end;
end if;
set period = 3600 * 10;
set cnt_c = ceil((en - beg) / period) ;
/*works quick enough*/
WHILE i < cnt_c DO
insert into tmp_c (
select uid, min(tm), i from events_view where
locate("1st_launch", e) > 0 and tm > (beg + period * i)
AND tm <= (beg + period * (i+1)) group by uid );
SET i = i+1;
END WHILE;
Cohorts may consist the same user ids, though usually one user is exist only in one cohort. And in each cohort users are unique.
Now I have temp table like this:
user_id | 1st timestamp | cohort_no
uid1 1423836540 0
uid2 1423839540 0
uid3 1423841160 1
uid4 1423841460 2
...
uidN 1423843080 M
Then I need to again divide time range on periods and calculate for each period how many users from each cohort have sent event "level_end:001:win".
For each small period I select all unique users who have sent "level_end:001:win" event and left join them to tmp_c cohorts table. So I have something like this:
user_id | 1st timestamp | cohort_no | user_id | other fields...
uid1 1423836540 0 uid1
uid2 1423839540 0 null
uid3 1423841160 1 null
uid4 1423841460 2 uid4
...
uidN 1423843080 M null
This way I see how many users from my cohorts are in those who have sent "level_end:001:win", exclude not found by where clause: where t2.uid is not null.
Finally I perform grouping and have counts of users in each cohort, who have sent "level_end:001:win" in this particluar period.
Here's the code:
DROP TABLE IF EXISTS tmp_res;
create temporary table tmp_res (uid varchar(64) CHARACTER SET cp1251 NOT NULL, c int(11), cnt int(11) );
set i = 0;
set cnt_c = ceil((t_end - beg) / period) ;
WHILE i < cnt_c DO
insert into tmp_res
select concat(beg + period * i, "_", beg + period * (i+1)), c, count(distinct(uid)) from
(select t1.uid, t1.c from tmp_c t1 left join
(select uid, min(tm) from events_view where
locate("level_end:001:win", e) > 0 and
tm > (beg + period * i) AND tm <= (beg + period * (i+1)) group by uid ) t2
on t1.uid = t2.uid where t2.uid is not null) t3
group by c;
SET i = i+1;
END WHILE;
/*getting result of the first method: tooo slooooow!*/
select * from tmp_res;
The result I've got (it's ok that some cohorts are not appear on some periods):
"1423832400_1423890000","1","35"
"1423832400_1423890000","2","3"
"1423832400_1423890000","3","1"
"1423832400_1423890000","4","1"
"1423890000_1423947600","1","21"
"1423890000_1423947600","2","50"
"1423890000_1423947600","3","2"
"1423947600_1424005200","1","9"
"1423947600_1424005200","2","24"
"1423947600_1424005200","3","70"
"1423947600_1424005200","4","6"
"1424005200_1424062800","1","7"
"1424005200_1424062800","2","15"
"1424005200_1424062800","3","21"
"1424005200_1424062800","4","32"
"1424062800_1424120400","1","7"
"1424062800_1424120400","2","13"
"1424062800_1424120400","3","24"
"1424062800_1424120400","4","18"
"1424120400_1424178000","1","10"
"1424120400_1424178000","2","12"
"1424120400_1424178000","3","18"
"1424120400_1424178000","4","14"
"1424178000_1424235600","1","6"
"1424178000_1424235600","2","7"
"1424178000_1424235600","3","9"
"1424178000_1424235600","4","12"
"1424235600_1424293200","1","6"
"1424235600_1424293200","2","8"
"1424235600_1424293200","3","9"
"1424235600_1424293200","4","5"
"1424293200_1424350800","1","5"
"1424293200_1424350800","2","3"
"1424293200_1424350800","3","11"
"1424293200_1424350800","4","10"
"1424350800_1424408400","1","8"
"1424350800_1424408400","2","5"
"1424350800_1424408400","3","7"
"1424350800_1424408400","4","7"
"1424408400_1424466000","2","6"
"1424408400_1424466000","3","7"
"1424408400_1424466000","4","3"
"1424466000_1424523600","1","3"
"1424466000_1424523600","2","4"
"1424466000_1424523600","3","8"
"1424466000_1424523600","4","2"
"1424523600_1424581200","2","3"
"1424523600_1424581200","3","3"
It works but it takes too much time to process because there are many queries here instead of one, so I need to rewrite it.
I think it can be rewritten with joins, but I'm still not sure how.
I decided to make a temporary table and write period boundaries in it:
DROP TABLE IF EXISTS tmp_times;
create temporary table tmp_times (tm_start int(11), tm_end int(11));
set cnt_c = ceil((t_end - beg) / period) ;
set i = 0;
WHILE i < cnt_c DO
insert into tmp_times values( beg + period * i, beg + period * (i+1));
SET i = i+1;
END WHILE;
Then I get periods-to-events mapping (user_id + timestamp represent particular event) to temp table and left join it to cohorts table and group the result:
SELECT Concat(tm_start, "_", tm_end) per,
t1.c coh,
Count(DISTINCT( t2.uid ))
FROM tmp_c t1
LEFT JOIN (SELECT *
FROM tmp_times t3
LEFT JOIN (SELECT uid,
tm
FROM events_view
WHERE Locate("level_end:101:win", e) > 0)
t4
ON ( t4.tm > t3.tm_start
AND t4.tm <= t3.tm_end )
WHERE t4.uid IS NOT NULL
ORDER BY t3.tm_start) t2
ON t1.uid = t2.uid
WHERE t2.uid IS NOT NULL
GROUP BY per,
coh
ORDER BY per,
coh;
In my tests this returns the same result as method #1. I can't check the result manually, but I understand how method #1 work more and as far I can see it gives what I want. Method #2 is faster, but I'm not sure it's the best way and it will give the same result even if cohorts will intersect.
Maybe there are well-known common methods to perform a cohort analysis in SQL? Is method #1 I use more reliable than method #2? I work with joins not that often, that's why still do not fully understand joins magic yet.
Method #2 looks like pure magic, and I used to not believe in what I don't understand :)
Thanks for answers!

complex hierarchal SQL query help needed please!

Calling all mySQL gurus!
I am in need of a complex query for mySQL but I can't get my head around it. There are 2 tables in question:
locations
(columns: location_id, parent, location)
Data is split in a hierarchal fashion into Country, Region, County and Town thus:
1, 0, England (country)
2, 1, South West (region)
3, 1, South East (region)
4, 2, Dorset (county)
5, 4, Bournemouth (town)
6, 4, Poole (town)
7, 4, Wimborne (town)
etc up to 400+ rows of location data
profiles
(columns: profile_id, title, location_id)
Each row has one location ID which is ALWAYS a town (ie the last child of). Eg:
1, 'This profile has location set as Bournemouth', 5
2, 'This profile has location set as Poole', 6
etc
What I need to achieve is to return all IDs from the Locations table where itself or it's children have entries associated with it. So in the example above I would need the following location IDs returned: 1, 2, 4, 5, 6
Reasons:
1 - YES, England is parent of South West, Dorset and Bournemouth which has an entry
2 - YES, South West is parent of Dorset and Bournemouth which has an entry
3 - NO, South East has no entries under it or any of it's children
4 - YES, Dorset is parent of Bournemouth which has an entry
5 - YES, Bournemouth has an entry
6 - YES, Poole has an entry
7 - NO, Wimborne has no entries
So, is this actually possible? I attempted to do it in PHP with nested SQL queries but the script timed out so there must be a way to do this just in a SQL query?
Thanking you in advance! :)
===========
UPDATE
After reading through and playing with all these solutions I realised that I was going about the problem completely the wrong way. Instead of going through all the locations and returning those that have entries it makes more sense and is far more efficient to get all the entries and return the corresponding locations and then go up the hierarchy to get each location parent until the root is hit.
Thank you very much for your help, it at least made me realise that what I was attempting was unnecessary.
The way I have dealt with this is doing only one SQL load, and then putting references inside of the parent objects.
$locations = array();
$obj_query = "SELECT * from locations";
$result_resource = mysql_query($obj_query);
while ($row = mysql_fetch_assoc($result_resource) {
$locations[$row['location_id'] = (object) $row;
}
foreach ($locations as $location) {
if (isset($location->parent) {
$locations[$location->parent]->children[] = $location;
}
}
Your object would then need a method such as this to find out whether a location is a descendant:
function IsAnscestorOF ($location) {
if (empty($children)) { return false; }
if (in_array($location, keys($this->children) {
return true;
} else {
foreach ($children as $child) {
if ($child->isAnscestor) {
return true;
}
}
}
return false;
}
THe fact that your script timed out would indicate an infinite loop somewhere.
Considering you're making a reference to the locations table based on the child area, plus another reference to the parent area, you probalby have to use a combination of PHP & Mysql to scroll through all this - a simple JOIN statement would not work in this case, I don't think.
Also you need to alter the table so that if it's a top-level page, it has a parent_id of NULL, not 0. After you've done that..
$sql = "SELECT * FROM locations WHERE parent =''";
$result = mysql_query($sql);
while($country = mysql_fetch_array($result)) {
$subsql = "SELECT * FROM locations WHERE parent='".$country['id']."'";
$subresult = mysql_query($subsql);
while($subregion = mysql_fetch_array($subresult)) {
$profilesql = "SELECT * FROM profiles WHERE location_id='".$subregion['id']."'";
$profileresult = mysql_query($profilesql);
echo mysql_num_rows($profileresult).' rows under '.$subregion['location'].'.<br />';
}
}
The base code is there... does anybody have a clever idea of making it work with various sub-levels? But honestly, if this were my project, I would have made separate tables for Country, and then Regions, and then City/Town. 3 tables would make the data navigation much easier.
If your php code was good you might have a nested loop in the [location -> parent] fd. I would start there first, and just use PHP. I don't think SQL has a recursive function.
If you NEED a nested parent loop, you should write an mutation of merge|union algorithm to solve this this.
To find the nested loop in PHP
$ids = array();
function nestedLoopFinder($parent)
{
global $ids;
$result = mysql_query("SELECT location_id FROM locations WHERE parent=$parent");
while($row = mysql_fetch_object($result))
{
if(in_array($row->location_id, $ids)) {
die("duplicate found: $row->location_id");
}
$ids[] = $row->location_id;
//recurse
nestedLoopFinder($row->location_id);
}
}
Not sure if I fully understand your requirements but the following stored procedure example might be a good starting point for you:
Example calls (note the included column)
mysql> call location_hier(1);
+-------------+---------------------+--------------------+---------------------+-------+----------+
| location_id | location | parent_location_id | parent_location | depth | included |
+-------------+---------------------+--------------------+---------------------+-------+----------+
| 1 | England (country) | NULL | NULL | 0 | 1 |
| 2 | South West (region) | 1 | England (country) | 1 | 1 |
| 3 | South East (region) | 1 | England (country) | 1 | 0 |
| 4 | Dorset (county) | 2 | South West (region) | 2 | 1 |
| 5 | Bournemouth (town) | 4 | Dorset (county) | 3 | 1 |
| 6 | Poole (town) | 4 | Dorset (county) | 3 | 1 |
| 7 | Wimborne (town) | 4 | Dorset (county) | 3 | 0 |
+-------------+---------------------+--------------------+---------------------+-------+----------+
7 rows in set (0.00 sec)
You'd call the stored procedure from php as follows:
$startLocationID = 1;
$result = $conn->query(sprintf("call location_hier(%d)", $startLocationID));
Full script:
http://pastie.org/1785995
drop table if exists profiles;
create table profiles
(
profile_id smallint unsigned not null auto_increment primary key,
location_id smallint unsigned null,
key (location_id)
)
engine = innodb;
insert into profiles (location_id) values (5),(6);
drop table if exists locations;
create table locations
(
location_id smallint unsigned not null auto_increment primary key,
location varchar(255) not null,
parent_location_id smallint unsigned null,
key (parent_location_id)
)
engine = innodb;
insert into locations (location, parent_location_id) values
('England (country)',null),
('South West (region)',1),
('South East (region)',1),
('Dorset (county)',2),
('Bournemouth (town)',4),
('Poole (town)',4),
('Wimborne (town)',4);
drop procedure if exists location_hier;
delimiter #
create procedure location_hier
(
in p_location_id smallint unsigned
)
begin
declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;
create temporary table hier(
parent_location_id smallint unsigned,
location_id smallint unsigned,
depth smallint unsigned default 0,
included tinyint unsigned default 0,
primary key (location_id),
key (parent_location_id)
)engine = memory;
insert into hier select parent_location_id, location_id, v_depth, 0 from locations where location_id = p_location_id;
create temporary table tmp engine=memory select * from hier;
/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
while not v_done do
if exists( select 1 from locations c
inner join tmp on c.parent_location_id = tmp.location_id and tmp.depth = v_depth) then
insert into hier select c.parent_location_id, c.location_id, v_depth + 1, 0 from locations c
inner join tmp on c.parent_location_id = tmp.location_id and tmp.depth = v_depth;
update hier inner join tmp on hier.location_id = tmp.parent_location_id
set hier.included = 1;
set v_depth = v_depth + 1;
truncate table tmp;
insert into tmp select * from hier where depth = v_depth;
else
set v_done = 1;
end if;
end while;
update hier inner join tmp on hier.location_id = tmp.parent_location_id
set hier.included = 1;
-- include any locations that have profiles ???
update hier inner join profiles on hier.location_id = profiles.location_id
set hier.included = 1;
-- output the results
select
c.location_id,
c.location as location,
p.location_id as parent_location_id,
p.location as parent_location,
hier.depth,
hier.included
from
hier
inner join locations c on hier.location_id = c.location_id
left outer join locations p on hier.parent_location_id = p.location_id
-- where included = 1 -- filter in your php or here up to you !
order by
hier.depth;
-- clean up
drop temporary table if exists hier;
drop temporary table if exists tmp;
end #
delimiter ;
call location_hier(1);
Hope this helps :)