SQL statement to randomly select one row having a certain criteria - mysql

Assuming that we have the following MySQL table:
ID | Name | Last_Name | Location |
1 | Alex | Griff | DT |
2 | John | Doe | York |
3 | Pat | Benat | DT |
4 | Jack | Darny | DT |
5 | Duff | Hill | York |
I want to create an sql statement that selects randomly one row of each location and store them in a new table.
For example:
2 | John | Doe | York |
3 | Pat | Benat | DT |
OR
4 | Jack | Darny | DT |
5 | Duff | Hill | York |
I would like to execute this on SQL since it's much faster than doing it on a Java program and using HashMap<K,V> and then storing the values again in another table.

This page has the solution. I simply modified the query to your table definition, as follows:
SELECT tmp.ID, tmp.Name, tmp.Last_Name, tmp.Location
FROM profiles
LEFT JOIN (SELECT * FROM profiles ORDER BY RAND()) tmp ON (profiles.Location = tmp.Location)
GROUP BY tmp.Location
ORDER BY profiles.Location;
SQL Fiddle demo

If you want a random sample for each location, you have several options. I think the easiest is a variable approach that will work well if your table is not super big.
select t.*
from (select t.*,
#rn := if(#location = location, #rn + 1, 1) as rn,
#location := location
from table t cross join
(select #location := '', #rn := 0) vars
order by location, rand()
) t
where rn = 1;
This assigns a sequential number to the locations and then chooses the first one.

Related

MYSQL How to get unique values from two tables mysql

I'm trying to get unique Value from two tables but the problem is the data in 'd.feedback' is displaying all of the data in the column feedback from daily_report table.
This is the code that I use. So what do you think I should do? Distinct fullname and feedback? or is there another alternative?
SET #row := 0;
SELECT DISTINCT CONCAT(#row := #row + 1,' ',ui.firstname) as 'Regular Users', d.feedback
FROM daily_report d
INNER JOIN userinfo ui ON d.userid = ui.id
Here is the First Table
| id | firstname |
| -- | ----------|
| 1 | Christian |
| 2 | Levi |
| 3 | Brian |
Here is the Second Table
| userid| feedback | id |
| -- | ----------|--------|
| 1 | Thanks | 1 |
| 2 | Arigato | 1 |
| 3 | Sure | 2 |
| 4 | Thank you | 2 |
| 5 | Thank you | 2 |
| 6 | Thank you | 2 |
Desired output
Regular User
feedback
1 Christian
Thanks
2 Christian
Arigato
3 Levi
Sure
4 Levi
Thank you
And Here is my problem
I''m am getting duplicate value like Number 2 and 4
I think this does what you want
SELECT CONCAT(#row := #row + 1,' ',ui.firstname) as 'Regular Users',
d.feedback
FROM (select distinct feedback, userid from daily_report) d INNER JOIN userinfo ui ON d.userid = ui.userid;```
This query allows DISTINCT to do its job.
SELECT DISTINCT ui.firstname as 'Regular Users',
d.feedback
FROM daily_report d INNER JOIN userinfo ui ON d.userid = ui.userid;
When you do DISTINCT CONCAT(#row := #row + 1,' ',ui.firstname) as 'Regular Users' you make every value of Regular Users different from the others, which defeats DISTINCT.

get empty instead of repeated value in query

I have a table like this
|num|id|name|prj|
| 1 | 1|abc | 1 |
| 2 | 1|efg | 1 |
| 3 | 1|cde | 1 |
| 4 | 2|zzz | 1 |
I want to run a query like this:
SELECT * FROM table WHERE prj=1 ORDER BY name
but printing out repeated values only once. I want to keep all the rows and I would like to do this at database level and not on the presentation layer (I know how to do it in php).
Desired result is
|num|id|name|prj|
| 1 | 1|abc | 1 |
| 3 | |cde | 1 |
| 2 | |efg | 1 |
| 4 | 2|zzz | 1 |
any hint on where to start from to build that query?
Use a session variable to test if the previous ID is the same as the current ID:
SELECT num, IF(#lastid = id, '', #lastid := id) AS id, name, prj
FROM table
CROSS JOIN (SELECT #lastid := null) x
ORDER BY table.id, name
DEMO
Note that you need to qualify table.id, because ORDER BY defaults to using the alias from the SELECT list if it's the same as a table column, and that would order the empty fields first.

How to write a MySQL SELECT Query to achieve this result?

I have an abstract problem which can be simplified as the following problem: Assume that we have two tables persons and names that look as follows:
SELECT * FROM persons;
+----+-------+--------+
| id | name | fan_of |
+----+-------+--------+
| 1 | alice | 2 |
| 2 | bob | 4 |
| 3 | carol | 1 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+-------+--------+
and
SELECT * FROM names;
+----+-------+--------+
| id | name | active |
+----+-------+--------+
| 1 | alice | 1 |
| 2 | bob | 1 |
| 3 | carol | 0 |
| 4 | dave | 1 |
+----+-------+--------+
Every person (a row in the persons) table is a fan of itself or another person (represented by that other persons id in the fan_of column). The names table contains names that can be active or inactive.
For a given offset k, I want to SELECT the persons (rows of persons) that have the k+1-th active name as their name or that have one of these people as their fans. For example, if the offset is 1, the second active name is bob and hence I want to select all people with the name bob plus the people that have one of these bobs as their fans, which is in this example the row of persons with id=4. This means that I want to have the result:
+----+------+--------+
| id | name | fan_of |
+----+------+--------+
| 2 | bob | 4 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+------+--------+
What I have so far is the following query:
1 SELECT * FROM persons WHERE
2 EXISTS (
3 SELECT * FROM (
4 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
5 ) AS selectedname WHERE (selectedname.name=persons.name)
6 )
7 OR
8 EXISTS (
9 SELECT * FROM(
10 SELECT * FROM persons WHERE EXISTS (
11 SELECT * FROM (
12 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
13 ) AS selectedname WHERE (selectedname.name=persons.name)
14 )
15 ) AS personswiththatname WHERE persons.id=personswiththatname.fan_of
16 );
It gives me the desired result from above but please note that it is inefficient because the lines 3-5 and 11-13 are the same.
I have the following two questions:
What can be done to avoid this inefficiency?
I actually need to distinguish between those rows that came from the
name condition (here the rows with name=bob) and those that came
from the fan_of condition (here the row with name=dave). This
could be done in the application code but then I would need another
database query before to find out the k+1-th active name and this might
be slow (please correct me if this is the better solution). I would
rather prefer an additional column z that helps me to distinguish
like
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 4 | dave | 3 | 0 |
| 5 | bob | 2 | 1 |
+----+------+--------+---+
How can such an output be achieved?
It looks like I can get the minimum you want to achieve using parameters (should this be an option).
It's not pretty, but I can't see a simple way of achieving what you're asking for, so this is what I have so far....(set #offset to suit 'k')
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*
From persons a
where (a.name = #name) OR (a.id IN (SELECT fan_of from persons where name = #name));
If you still don't have an answer by the time I've had food, I'll look at part 2.
(hopefully I've read your brief correctly)
P.S. I've kept the #name SQL in a single line as it seems to read better in this context.
Edit: Here's a pretty messy but functional indicator of source, using your example. Z = 1 is where the row is from the name, '0' is from fan_of
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*,'1' as z
From persons a
where (a.name = #name)
union
select
a.*,'0' as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name));
Distinct ID Query:
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
SELECT id, name, fan_of, z FROM
(select
distinct a.id,
a.name,
a.fan_of,
1 as z
From persons a
where (a.name = #name)
union
select
distinct a.id,
a.name,
a.fan_of,
0 as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name))
ORDER BY z desc) qry
GROUP BY id;
This produces:
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 5 | bob | 2 | 1 |
| 4 | dave | 3 | 0 |
+----+------+--------+---+

Combining two unrelated SELECT-s with different ORDER-s into one result

TL;DR: How to combine two differently ordered SELECTs from unrelated tables into one result?
Let's say I have 2 non-related tables - cats and games.
Table "cats":
+----+----------------+-----+
| id | name | age |
+----+----------------+-----+
| 1 | Balthazar | 3 |
| 2 | Milkman | 7 |
| 3 | The Dark Angel | 4 |
+----+----------------+-----+
Table "games"
+----+----------+-------+
| id | name | plays |
+----+----------+-------+
| 1 | Snake | 18 |
| 2 | Lemmings | 234 |
| 3 | Ludo | 33 |
+----+----------+-------+
For each of the tables respecitively I need a SELECT to order them, and get different columns:
SELECT age FROM `cats` ORDER BY age DESC;
and
SELECT plays FROM `games` ORDER BY plays DESC;
Thing is I want to get both results using one query, instead of two. The expected result:
+-----+-------+
| age | plays |
+-----+-------+
| 7 | 234 |
| 4 | 33 |
| 3 | 18 |
+-----+-------+
No point for a JOIN as the tables share no relation. I was thinking of using UNION but I can't figure out how to handle the fact that both queries:
select different columns
order the result by different columns
Any ideas how to achieve this?
One method is to calculate a row number and then join on that value:
select rn, max(age) as age, max(plays) as plays
from ((select c.age, NULL as plays, (#rnc := #rnc + 1) as rn
from cats c cross join (select #rnc := 0) params
) union all
(select NULL as age, g.plays, (#rng := #rng + 1) as rn
from games g cross join (select #rng := 0) params
)
) cg
group by rn;
select age,plays from cats inner join games on cats.id=games.id

Interpolate rows into MySQL database

I have data which is formed like this:
+----------+-------+-------+
| DAY | VALUE | Name |
+----------+-------+-------+
| 01/01/14 | 1030 | BOB
| 01/02/14 | 1020 | BOB
| 01/03/14 | 1080 | BOB
| 01/04/14 | 1090 | BOB
| 01/05/14 | 1040 | BOB
| 01/08/14 | 1030 | BOB
| 01/11/14 | 4030 | BOB
| 01/12/14 | 5000 | BOB
| 01/13/14 | 6000 | BOB
| 01/14/14 | 1096 | BOB
| 01/14/14 | 1200 | MIKE
| 01/15/14 | 1040 | MIKE
| 01/16/14 | 1600 | MIKE
| 01/17/14 | 1070 | MIKE
| 01/18/14 | 1340 | MIKE
| 01/19/14 | 1060 | MIKE
| 01/01/14 | 6000 | JANE
| 01/02/14 | 1700 | JANE
| 01/03/14 | 1070 | JANE
| 01/04/14 | 8000 | JANE
+----------+-------+------+
For each name there needs to be a row for the dates between 01/01/14 to 02/01/14 (1 month). As you can see Bob, Mike, and Jane (although in my real database there are thousands of names) are all missing dates between this time period. I would like to somehow insert the missing rows by interpolation of some sort. For example Bob is missing 01/06/14 and 01/07/14. I would like it to interpolate by adding these two dates and then the values to be the average of the two field between so these two missing fields would both have the value ((1040+1030)/2) = 1035. If there is no data before like for MIKE (starts at 01/14/14) I would like all the new rows to have 01/14/14 value now. I have tried various different techniques such as using coalesce command, cursors, but can't get it to work. Also I am not set on having these EXACT values, if there is some sort of math library which can interpolate I would be open to this as well. Thanks.
You have two problems, generating the rows and interpolating the values. You can generate the rows with this SQL:
select d.day, n.name, t.value
from (select distinct name from table t) n cross join
(select distinct day from table t) d left outer join
table t
on t.name = n.name and t.day = d.day;
Doing the interpolation is troublesome. You can do this using variables and multiple sorting. Here is logic:
select day, name, value, prev_value,
#value as next_value,
#value := if(#name = name and value is not null, value, #value),
#name := name
from (select d.day, n.name, t.value,
#value as prev_value,
#value := if(#name = name and value is not null, value, #value),
#name := name
from (select distinct name from table t) n cross join
(select distinct day from table t) d left outer join
table t
on t.name = n.name and t.day = d.day cross join
(select #name := '', #value := NULL) vars
order by n.name, d.day
) t cross join
(select #name := '', #value := NULL) vars
order by n.name, d.day desc;
This will probably work for you, but it is depending on MySQL evaluating the expressions in order in each select (for the assignment of variables). You can make the syntax more complicated to fix this, but that would hide the logic. You can now implement the logic that you want:
select day, name,
(case when value is not null then value
when prev_value is not null and next_value is not null
then (prev_value + next_value) / 2
when prev_value is null then next_value
else prev_value
end) as value
from (<previous query here>) t;