I have this query :
select
name
from
provinces
WHERE
province_id IN(1,3,2,1)
ORDER BY FIELD(province_id, 1,3,2,1)
the Number of values in IN() are dynamic
How can I get all rows even duplicates ( in this example -> 1 ) with given ORDER BY ?
the result should be like this :
name1
name3
name2
name1
plus I shouldn't use UNION ALL :
select * from provinces WHERE province_id=1
UNION ALL
select * from provinces WHERE province_id=3
UNION ALL
select * from provinces WHERE province_id=2
UNION ALL
select * from provinces WHERE province_id=1
You need a helper table here. On SQL Server that can be something like:
SELECT name
FROM (Values (1),(3),(2),(1)) As list (id) --< List of values to join to as a table
INNER JOIN provinces ON province_id = list.id
Update: In MySQL Split Comma Separated String Into Temp Table can be used to split string parameter into a helper table.
To get the same row more than once you need to join in another table. I suggest to create, only once(!), a helper table. This table will just contain a series of natural numbers (1, 2, 3, 4, ... etc). Such a table can be useful for many other purposes.
Here is the script to create it:
create table seq (num int);
insert into seq values (1),(2),(3),(4),(5),(6),(7),(8);
insert into seq select num+8 from seq;
insert into seq select num+16 from seq;
insert into seq select num+32 from seq;
insert into seq select num+64 from seq;
/* continue doubling the number of records until you feel you have enough */
For the task at hand it is not necessary to add many records, as you only need to make sure you never have more repetitions in your in condition than in the above seq table. I guess 128 will be good enough, but feel free to double the number of records a few times more.
Once you have the above, you can write queries like this:
select province_id,
name,
#pos := instr(#in2 := insert(#in2, #pos+1, 1, '#'),
concat(',',province_id,',')) ord
from (select #in := '0,1,2,3,1,0', #in2 := #in, #pos := 10000) init
inner join provinces
on find_in_set(province_id, #in)
inner join seq
on num <= length(replace(#in, concat(',',province_id,','),
concat(',+',province_id,',')))-length(#in)
order by ord asc
Output for the sample data and sample in list:
| province_id | name | ord |
|-------------|--------|-----|
| 1 | name 1 | 2 |
| 2 | name 2 | 4 |
| 3 | name 3 | 6 |
| 1 | name 1 | 8 |
SQL Fiddle
How it works
You need to put the list of values in the assignment to the variable #in. For it to work, every valid id must be wrapped between commas, so that is why there is a dummy zero at the start and the end.
By joining in the seq table the result set can grow. The number of records joined in from seq for a particular provinces record is equal to the number of occurrences of the corresponding province_id in the list #in.
There is no out-of-the-box function to count the number of such occurrences, so the expression at the right of num <= may look a bit complex. But it just adds a character for every match in #in and checks how much the length grows by that action. That growth is the number of occurrences.
In the select clause the position of the province_id in the #in list is returned and used to order the result set, so it corresponds to the order in the #in list. In fact, the position is taken with reference to #in2, which is a copy of #in, but is allowed to change:
While this #pos is being calculated, the number at the previous found #pos in #in2 is destroyed with a # character, so the same province_id cannot be found again at the same position.
Its unclear exactly what you are wanting, but here's why its not working the way you want. The IN keyword is shorthand for creating a statement like ....Where province_id = 1 OR province_id = 2 OR province_id = 3 OR province_id = 1. Since province_id = 1 is evaluated as true at the beginning of that statement, it doesn't matter that it is included again later, it is already true. This has no bearing on whether the result returns a duplicate.
Related
I've been searching through StackOverflow and the closest one that I've found is to use custom variable inside subquery. But the suggested solution has two shortcomings.
Table
+----+-------+-------------+
| id | type | MyAmountCol |
+----+-------+-------------+
| 1 | 85482 | 10 |
+----+-------+-------------+
| 2 | 47228 | 20 |
+----+-------+-------------+
| 3 | 12026 | 40 |
+----+-------+-------------+
When every row has cannot meet the condition, (i.e. if every value is larger than the threshold value.) no row is returned.
Example Fiddle
Query
SET #runningTotal=0;
SELECT
O.Id,
O.Type,
O.MyAmountCol,
#runningTotal + O.MyAmountCol as 'RunningTotal',
#runningTotal := #runningTotal + O.MyAmountCol
FROM Table1 O
HAVING RunningTotal <=5;
Returned
0 Row(s)
When the condition is caught in the middle, (i.e. if the first two values are 10 and 20, and the threshold is 15) the sum of returned values are always less than or equal to the threshold.
Example Fiddle
Query
SET #runningTotal=0;
SELECT
O.Id,
O.Type,
O.MyAmountCol,
#runningTotal + O.MyAmountCol as 'RunningTotal',
#runningTotal := #runningTotal + O.MyAmountCol
FROM Table1 O
HAVING RunningTotal <=15;
Returned
1 85482 10 10 (1 Row)
The desired result is this. In the first example fiddle, I want the first row (id=1, type=85842) returned. In the second example fiddle, I want rows with id=1 , type=85842 and id=2, type=47228 returned.
Putting it differently, what I'm trying to do is slightly different from what I've found and it doesn't seem to achieve it with that approach. I want the fewest number of sequential rows that exceed the target value. Is there any way for this with only MySQL (query), or should I solve this in the application level?
JOIN the table to itself on lesser ids (because that's what you're ordering on), summing all the rows from the join and keeping those rows whose sum is less than (not less than or equal) the threshold:
SELECT
a.Id,
a.Type,
a.MyAmountCol
FROM Table1 a
LEFT JOIN Table1 b on b.id < a.id
GROUP BY 1,2,3
HAVING COALESCE(SUM(b.MyAmountCol), 0) < 15
The COALESCE() call is added to cater for the lowest id, which we want to keep (always), having no rows to join with.
Disclaimer: Code may not compile or work as it was thumbed in on my phone (but there's a reasonable chance it will work)
I need output in following order(firstly, group by last 3 letters and then arrange in order based on the first 3 digits)
ColumnA
001_eng
004_eng
002_chn
003_usa
But order by ColumnA gives me
ColumnA
001_eng
002_chn
003_usa
004_eng
This is just sample data. I have hundreds of entries in this format and the values keep changing everyday. So, specifying all the entries inside the field is not a feasible option.
I'm not sure of how to use FIELD() in my case.
You can use FIELD:
select *
from tablename
order by
FIELD(ColumnA, '001_eng', '004_eng', '002_chn', '003_usa')
(please be careful if ColumnA is not in the list the field function will return 0 and the rows will be put on top)
or you can use CASE WHEN:
select *
from tablename
order by
case
when ColumnA='001_eng' then 1
when ColumnA='004_eng' then 2
when ColumnA='002_chn' then 3
when ColumnA='003_usa' then 4
else 5
end
or you can use a different languages table where you specify the order:
id | name | sortorder
1 | 001_eng | 1
2 | 002_chn | 3
3 | 003_usa | 4
4 | 004_eng | 2
then you can use a join
select t.*
from
tablename t inner join languages l
on t.lang_id = l.id
order by
l.sortorder
(with proper indexes this would be the better solution with optimal performances)
You can use SUBSTRING_INDEX in case all ColumnA values are formatted like in the sample data:
SELECT *
FROM mytable
ORDER BY FIELD(SUBSTRING_INDEX(ColumnA, '_', -1), 'eng', 'chn', 'usa'),
SUBSTRING_INDEX(ColumnA, '_', 1)
Demo here
you can use substring() and get order by
SELECT *
FROM table_name
ORDER BY SUBSTRING(ColumnA, -7, 3);
I want count the length of a comma separated column
I have use these
(LENGTH(Col2) - LENGTH(REPLACE(Col2,",","")) + 1)
in my select query.
Demo:
id | mycolumn
1 2,5,8,60
2 4,5,1
3 5,Null,Null
query result for first two row is coming correctly.for 1 = 4 ,2 = 3 but for 3rd row it is calculating null value also.
Here is what I believe the actual state of your data is:
id | mycolumn
1 2,5,8,60
2 4,5,1
3 NULL
In other words, the entire value for mycolumn in your third record is NULL, likely from doing an operation involving a NULL value. If you actually had the text NULL your current query should still work.
The way to get around this would be to use COALESCE(val, "") when handling the NULL values in your strings.
Crude way of doing it is to replace the occurances of ',Null' with nothing first:-
SELECT a.id, (LENGTH(REPLACE(mycolumn, ',Null', '')) - LENGTH(REPLACE(REPLACE(mycolumn, ',Null', ''),",","")) + 1)
FROM some_table a
If the values refer to the id of rows in another table then you can join against that table using FIND_IN_SET and then count the matches (assuming that the string 'Null' is not an id on that other table)
SELECT a.id, COUNT(b.id)
FROM some_table a
INNER JOIN id_list_table b
ON FIND_IN_SET(b.id, a.mycolumn)
GROUP BY a.id
I need a query that find the recommended TV shows for an user, based on the TV Shows he is following.
Do to this I have the following tables:
the table Progress that contains wich show the user is following and the percentage of seen episodes (to solve this problem we can assume I have only one user in the database)
the table Suggested that contains _id1,_id2 and value (value is the strength of the connections between the show with id=_id1 and the show with id=_id2: the more value is great, the more the shows have something in common).
Note that in this table applies the commutative property, so the strength of the connection between id1 and _id2 is the same of _id1 and _id2. Moreover there aren't two rows such as ROW1._id1=ROW2._id2 AND ROW1._id2 = ROW2._id1
the table ShowCache that contains the details about a TV Show, such as name etc..
The following query is what I'm trying to do, but the result is an empty set:
SET #a = 0; //In other tests this line seem to be necessary
SELECT `ShowCache`.*,
(SUM(value) * (Progress.progress)) as priority
FROM `Suggested`,`ShowCache`, Progress
WHERE
((_id2 = Progress.id AND _id1 NOT IN (SELECT id FROM Progress) AND #a:=_id1)//There is a best way to set a variable here?
OR
(_id1 = Progress.id AND _id2 NOT IN (SELECT id FROM Progress) AND #a:=_id2))
AND `ShowCache`._id = #a //I think that the query fails here
GROUP BY `ShowCache`._id
ORDER BY priority DESC
LIMIT 0,20
I know the problem is related to the usage of variables, but I can't solve it. Any help is really appreciated.
PS: the main problem is that (because of the commutative propriety), without variables I need two queries, wich takes about 3 secs to begin executed (the query is more complex than the above). I'm really trying to make a single query to do this task
PPS: I tied also with an XOR operation, that results in an infinite loop?!?!? Here's the WHERE clause I tried:
((_id2=Progress.id AND #a:=_id1) XOR (_id1=Progress.id AND #a:=_id2)) AND `ShowCache`._id = #a
EDIT:
I come up with this WHERE conditions without using any variable:
(_id2 = Progress.id OR _id1 = Progress.id)
AND `ShowCache`._id = IF(_id2 = Progress.id, _id1,_id2)
AND `ShowCache`._id NOT IN (SELECT id FROM Progress)
It works, but it is very slow.
Your attempt to use xor is clever. If you want to get the nonmatching value you want to use bitwise XOR which is ^
Progress.id ^_id1 ^ _id2
3 ^ 2 ^ 3 = 2
2 ^ 2 ^ 3 = 3
You can use this trick to setup a join and really simplify your query (eliminate the OR's and NOT IN's and do it in one query without variables.)
select users.name as username, showcache.name as show_name,
sum(progress * value) as priority from users
inner join progress on users.id = progress.user_id
inner join suggested on progress.show_id in (suggested.id_1, suggested.id_2)
inner join showcache on showcache.id =
(suggested.id_1 ^ suggested.id_2 ^ progress.show_id)
where showcache.id not in
(select show_id from progress where user_id = users.id)
group by showcache.id
order by priority desc;
I also setup a fiddle to demonstrate it:
http://sqlfiddle.com/#!2/2dcd8/24
To break it down. I created a users table with a single user (but the solution will work with multiple users.)
The select and join to progress is straightforward. The join to suggested uses IN as an alternative to writing it with OR
The join to showcache is where the bitwise XOR happens. One of the id's links up to the progress.show_id and we want to use the other one.
It does include a not in to exclude shows already watched from the results. I could have changed it to not exists? but it seems clearer this way.
You're setting #a's value twice within the where clause, meaning that the query is actually boiling down to:
...
WHERE ... AND `ShowCache`._id = _id2
MySQL evalutes variable assignments in a first-encountered order, so you should leave #a constant until the END of the clause, then assign a new value, e.g
mysql> set #a=5;
mysql> select #a, #a+1, #a*5, #a := #a + 1, #a;
+------+------+------+--------------+------+
| #a | #a+1 | #a*5 | #a := #a + 1 | #a |
+------+------+------+--------------+------+
| 0 | 1 | 0 | 1 | 1 |
| 1 | 2 | 5 | 2 | 2 |
| 2 | 3 | 10 | 3 | 3 |
+------+------+------+--------------+------+
Note that #a's value in the first 3 columns remains constant, UNTIL mysql reaches the #a := #a +1, after which #a has a new value
So perhaps your query should be
set #a = 0;
select #temp := #a, ..., #a := _id2
where
((_id2 = Progress.id AND _id1 NOT IN (SELECT id FROM Progress) AND #temp =_id1)
...
etc...
I have a query problem about retrieving number of rows that the counting will start from the specified row. These are the row values on my MySQL database.
John Parker
Tony Graham
Perter Smith
Annabelle Sergio
Kris Pata
Neshren Luca
Paul Pon
Zervich Nuckrav
Allan Paulson
Imanu Hashmarida
Varick Dagovich
Senster Burgsman
Lito Umani
Ramsay Nudillo
Now I want to retrieve the first 5 row that will start from Neshren Luca so that the resultset may look like this:
Neshren Luca
Paul Pon
Zervich Nuckrav
Allan Paulson
Imanu Hashmarida
How may I do that?
Note: I will not try to retrieve the row values base on number of row but base from a specific row value.
This looks very ugly but have this a try, this uses local variable
SET #selectedName := 'Neshren Luca'; -- set the name here
SET #selectRow :=
(
SELECT RankNo
FROM
(
SELECT #rowNum := #rowNum + 1 AS RankNo,
a.Names
FROM tableName a, (SELECT #rowNum:= 0) b
) x
WHERE Names = #selectedName
);
SELECT Names
FROM
(
SELECT #rowNum1 := #rowNum1 + 1 AS RankNo, a.Names
FROM tableName a, (SELECT #rowNum1:= 0) b
) x
WHERE RankNo BETWEEN #selectRow AND #selectRow + 4
SQLFiddle Demo
Looks like the above select statement will work fine for that..other wise you have to do other way but it will bit lengthy..that..
-> You have to get the above records into a cursor
-> Looping through the cursor and getting the records..
SQL engine may have some 'natural' row order, but it is not guaranteed. You should have primary key, say ID, then ORDER BY ID, find first ID WHERE your string is. And then select first N items WHERE ID >= that id.
Edit: Or, if your database guarantees natural order, find row id/index and LIMIT/OFFSET by this value. SQLite has built-in natural rowid for each table row, for example.