Select the same row and display it many times - php

I need to make a select that returns objects with IDs in specified array. But it would be very helpful if I could get in return the same object as many times as it was specified in closure. Even that this object is only once in database. For example
SELECT * FROM `T` WHERE `id` IN (1, 1, 2)
I would like it to return 3 rows - 2x first one. And it's important for me that these results are in order that was specified in closure. Is this possible with MYSQL?

You could do something like this:
SELECT * FROM T WHERE item_id = 1
UNION ALL
SELECT * FROM T WHERE item_id = 1
UNION ALL
SELECT * FROM T WHERE item_id = 2
Using union all, you concatenate all the statements (which only return 1 row)

You can also write.
Because UNION ALL always uses a memory table to hold the results.
You can keep the memory table much smaller when you only define numbers.
I also assume that the column id is a auto_incremented column with a primary key.
Without a index on the id column joining isn't a great idea.
SELECT
T.*
FROM (
SELECT
1 AS number
FROM DUAL
UNION ALL
SELECT
1 AS number
FROM DUAL
UNION ALL
SELECT
2 AS number
FROM DUAL
)
AS numbers
INNER JOIN
T
ON
T.id = numbers.number
ORDER BY
numbers.number ASC

Related

Select rows where any of field's values matches values from array

I have a pretty complicated task. I need to select rows which match any of an array's value - BUT the field contains many comma-seperated values as well.
$teamlist = "25,26,27,28,29,30"
MYSQL-Row 1 = "26,29,31,35,36"
MYSQL-Row 2 = "30,31,32,36,39"
MYSQL-Row 3 = "31,35,36,37,38"
MYSQL-Row 4 = "23,26,29,30,31"
As result Rows 1,2 and 4 should be selected.
I tried something like:
mysqli_query($con,"SELECT * FROM user_meta WHERE team IN '".$ids."'");
But that only works if the fields only contain one id, not multiple. I am stuck. Any ideas?
Thanks!
You could pass your parameters as a derived table and then use find_in_set() to search the csv column:
select t.*
from mytable t
inner join (
select 25 team_id
union all select 26
union all select 27
...
) x on find_in_set(x.team_id, t.team)
This leaves you with the work of building the derived table from your application. In very recent versions of MySQL, the VALUES() statement makes the task a litte easier:
select t.*
from mytable t
inner join (values row(26),row(27), ...) x(team_id)
on find_in_set(x.team_id, t.team)
However, you should not be storing lists in a database columns. This is hurtful in many ways, as explained in this famous SO answer. You should be storing each value of the each list on a separate row.

How can I get first 5 and last 1 records from table mysql?

I'm working on php small project, here I need first 5 records from beginning of records and last record 1 from end of the table's record. I don't know how to write a single mysqli query.
Any help will be appreciated. Thanks in advance.
SQL tables represent unordered sets. So, there is no such thing as the first five rows or last row -- unless a column explicitly defines the ordering.
Often, a table has some sort of auto-incremented id column, which can be used for this purpose. If so, you can do:
(select t.*
from t
order by id asc
limit 5
) union all
(select t.*
from t
order by id desc
limit 1
);
Notes:
Sometimes, an insert date/time column is the appropriate column to use.
You want to use union all rather than union -- unless you want to incur the overhead of removing duplicate values.
For this formulation, if there are fewer than 6 rows, then you will get a duplicate.
The UNION operator allows this, carefully toying with the ORDER BY and LIMIT clauses :
(SELECT * FROM table ORDER BY field ASC LIMIT 5)
UNION
(SELECT * FROM table ORDER BY field DESC LIMIT 1)

How to count same string in an array

I have a little problem , I want to count same string in an array ,
for example
My table like this:
id | data
---------------------------
1 | #user1,#user2,#user3
2 | #user1,#user4
3 | #user1,#user5
4 | #user2,#user3
How can I count #user1,#user2,etc.. ?
You can use find_in_set to find data in comma separated field.
SELECT COUNT(*)
FROM some_table
WHERE FIND_IN_SET('#user2', data)
This will give you a count of the rows that contain this string.
Note that this does suggest a database design that is not normalised and as this function can't use indexes it is likely to perform badly compared to a properly normalised database (ie, split the strings off onto a different table, with one row per string per id).
EDIT - if you want a count of all the strings:-
SELECT sub1.aString, COUNT(*)
FROM
(
SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(data, ',', 1 + units.i + 10 * tens.i), ',', -1) AS aString
FROM some_table,
(SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units,
(SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
) sub1
INNER JOIN some_table
ON FIND_IN_SET(sub1.aString, data)
GROUP BY sub1.aString
This uses a couple of sub queries to generate 100 rows for each row in you main table, each with a combination of the numbers 0 to 9 twice. From the combination it can calculate a number between 0 and 99 (can easily be expanded to add another sub query to go from 0 to 999, or more). It then uses SUBSTRING_INDEX with the generated number to split out the possible strings in data for each row. This will generate a LOT of duplicates, partly as the strings will likely be on many rows and partly because the last string on each row will be put out many times (ie, if there are 10 string, the last one will be put out 91 times due to the way SUBSTRING_INDEX is used). DISTINCT is used to remove these duplicates.
The result is then joined against your table using FIND_IN_SET, and COUNT / GROUP BY used to get all the counts of all the strings.
You can try somthing like this:-
SELECT COUNT(data)
FROM your_table
WHERE data LIKE '%#user1%'

combine four table into one new table

SELECT a.delivery_date,
a.delivery_hour,
a.price as EX-ANTE,
FROM mms_realtime_dispatch_prices_report a
UNION ALL
SELECT b.delivery_date,
b.delivery_hour,
b.price as EX-POST,
FROM mms_realtime_dispatch_prices_report b
UNION ALL
SELECT c.region,
c.dem_rtdel,
c.date,
FROM pub_demand_lwap c;
UNION ALL
SELECT region,
report,
hour,
SUM(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10,q11)
FROM pub_markets_bids_and_offers
WHERE delivery date=03/16/2011
GROUP BY hour
help! need to combine this four table into one new table no duplicate data
Can you help me in combining this four tables into one table. this is the first time i encounter this. I really need a help :(
In SQL server union works only if there is same number and type of columns return by query.
You need to get your Union query right 1st I can see so many things wrong with Query At the moment,
Your number of columns retunred by each select arent same, Last query is returning 4 columns and other 3,
You are Aliasing Columns in 2nd query, it will not have any effect as Only the Column Names from very first select statement are visible in the result set.
Guessing from the Column names you have Different Data Types that
you are trying to UNION. Datatypes Returned from all selects that you are using in UNION should return the same datatypes e.g
SELECT Column1_DataType1, Column2_DataType2, Column3_DataType3 FROM Table_Name1
UNION ALL
SELECT Column1_DataType1, Column2_DataType2, Column3_DataType3 FROM Table_Name2
UNION ALL
SELECT Column1_DataType1, Column2_DataType2, Column3_DataType3 FROM Table_Name3
and so on....
Once you have met all these condition then you can do something like this to eliminate duplicate rows from you result set
;with CTE
AS
(
SELECT ID_Column, rn = ROW_NUMBER() OVER (PARTITION BY Column1, Column2, Column3... ORDER BY ID ASC)
FROM ( -- All of your UNION ALL Statements Can go here --)q
)
DELETE FROM CTE
WHERE rn = 1

How to quickly SELECT 3 random records from a 30k MySQL table with a where filter by a single query?

Well, this is a very old question never gotten real solution. We want 3 random rows from a table with about 30k records. The table is not so big in point of view MySQL, but if it represents products of a store, it's representative. The random selection is useful when one presents 3 random products in a webpage for example. We would like a single SQL string solution that meets these conditions:
In PHP, the recordset by PDO or MySQLi must have exactly 3 rows.
They have to be obtained by a single MySQL query without Stored Procedure used.
The solution must be quick as for example a busy apache2 server, MySQL query is in many situations the bottleneck. So it has to avoid temporary table creation, etc.
The 3 records must be not contiguous, ie, they must not to be at the vicinity one to another.
The table has the following fields:
CREATE TABLE Products (
ID INT(8) NOT NULL AUTO_INCREMENT,
Name VARCHAR(255) default NULL,
HasImages INT default 0,
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The WHERE constraint is Products.HasImages=1 permitting to fetch only records that have images available to show on the webpage. About one-third of records meet the condition of HasImages=1.
Searching for a Perfection, we first let aside the existent Solutions that have drawbacks:
I. This basic solution using ORDER BY RAND(),
is too slow but guarantees 3 really random records at each query:
SELECT ID, Name FROM Products WHERE HasImages=1 ORDER BY RAND() LIMIT 3;
*CPU about 0.10s, scanning 9690 rows because of WHERE clause, Using where; Using temporary; Using filesort, on Debian Squeeze Double-Core Linux box, not so bad but
not so scalable to a bigger table as temporary table and filesort are used, and takes me 8.52s for the first query on the test Windows7::MySQL system. With such a poor performance, to avoid for a webpage isn't-it ?
II. The bright solution of riedsio using JOIN ... RAND(),
from MySQL select 10 random rows from 600K rows fast, adapted here is only valid for a single random record, as the following query results in an almost always contiguous records. In effect it gets only a random set of 3 continuous records in IDs:
SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT (RAND() * (SELECT MAX(ID) FROM Products)) AS ID)
AS t ON Products.ID >= t.ID
WHERE (Products.HasImages=1)
ORDER BY Products.ID ASC
LIMIT 3;
*CPU about 0.01 - 0.19s, scanning 3200, 9690, 12000 rows or so randomly, but mostly 9690 records, Using where.
III. The best solution seems the following with WHERE ... RAND(),
seen on MySQL select 10 random rows from 600K rows fast proposed by bernardo-siu:
SELECT Products.ID, Products.Name FROM Products
WHERE ((Products.Hasimages=1) AND RAND() < 16 * 3/30000) LIMIT 3;
*CPU about 0.01 - 0.03s, scanning 9690 rows, Using where.
Here 3 is the number of wished rows, 30000 is the RecordCount of the table Products, 16 is the experimental coefficient to enlarge the selection in order to warrant the 3 records selection. I don't know on what basis the factor 16 is an acceptable approximation.
We so get at the majority of cases 3 random records and it's very quick, but it's not warranted: sometimes the query returns only 2 rows, sometimes even no record at all.
The three above methods scan all records of the table meeting WHERE clause, here 9690 rows.
A better SQL String?
Ugly, but quick and random. Can become very ugly very fast, especially with tuning described below, so make sure you really want it this way.
(SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT RAND()*(SELECT MAX(ID) FROM Products) AS ID) AS t ON Products.ID >= t.ID
WHERE Products.HasImages=1
ORDER BY Products.ID
LIMIT 1)
UNION ALL
(SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT RAND()*(SELECT MAX(ID) FROM Products) AS ID) AS t ON Products.ID >= t.ID
WHERE Products.HasImages=1
ORDER BY Products.ID
LIMIT 1)
UNION ALL
(SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT RAND()*(SELECT MAX(ID) FROM Products) AS ID) AS t ON Products.ID >= t.ID
WHERE Products.HasImages=1
ORDER BY Products.ID
LIMIT 1)
First row appears more often than it should
If you have big gaps between IDs in your table, rows right after such gaps will have bigger chance to be fetched by this query. In some cases, they will appear significatnly more often than they should. This can not be solved in general, but there's a fix for a common particular case: when there's a gap between 0 and the first existing ID in a table.
Instead of subquery (SELECT RAND()*<max_id> AS ID) use something like (SELECT <min_id> + RAND()*(<max_id> - <min_id>) AS ID)
Remove duplicates
The query, if used as is, may return duplicate rows. It is possible to avoid that by using UNION instead of UNION ALL. This way duplicates will be merged, but the query no longer guarantees to return exactly 3 rows. You can work around that too, by fetching more rows than you need and limiting the outer result like this:
(SELECT ... LIMIT 1)
UNION (SELECT ... LIMIT 1)
UNION (SELECT ... LIMIT 1)
...
UNION (SELECT ... LIMIT 1)
LIMIT 3
There's still no guarantee that 3 rows will be fetched, though. It just makes it more likely.
SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT (RAND() * (SELECT MAX(ID) FROM Products)) AS ID) AS t ON Products.ID >= t.ID
WHERE (Products.HasImages=1)
ORDER BY Products.ID ASC
LIMIT 3;
Of course the above is given "near" contiguous records you are feeding it the same ID every time without much regard to the seed of the rand function.
This should give more "randomness"
SELECT Products.ID, Products.Name
FROM Products
INNER JOIN (SELECT (ROUND((RAND() * (max-min))+min)) AS ID) AS t ON Products.ID >= t.ID
WHERE (Products.HasImages=1)
ORDER BY Products.ID ASC
LIMIT 3;
Where max and min are two values you choose, lets say for example sake:
max = select max(id)
min = 225
This statement executes really fast (19 ms on a 30k records table):
$db = new PDO('mysql:host=localhost;dbname=database;charset=utf8', 'username', 'password');
$stmt = $db->query("SELECT p.ID, p.Name, p.HasImages
FROM (SELECT #count := COUNT(*) + 1, #limit := 3 FROM Products WHERE HasImages = 1) vars
STRAIGHT_JOIN (SELECT t.*, #limit := #limit - 1 FROM Products t WHERE t.HasImages = 1 AND (#count := #count -1) AND RAND() < #limit / #count) p");
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
The Idea is to "inject" a new column with randomized values, and then sort by this column. The generation of and sorting by this injected column is way faster than the "ORDER BY RAND()" command.
There "might" be one caveat: You have to include the WHERE query twice.
What about creating another table containing only items with image ? This table will be much lighter as it will contain only one-third of the items the original table has !
------------------------------------------
|ID | Item ID (on the original table)|
------------------------------------------
|0 | 0 |
------------------------------------------
|1 | 123 |
------------------------------------------
.
.
.
------------------------------------------
|10 000 | 30 000 |
------------------------------------------
You can then generate three random IDs in the PHP part of the code and just fetch'em the from the database.
I've been testing the following bunch of SQLs on a 10M-record, poorly designed database.
SELECT COUNT(ID)
INTO #count
FROM Products
WHERE HasImages = 1;
PREPARE random_records FROM
'(
SELECT * FROM Products WHERE HasImages = 1 LIMIT ?, 1
) UNION (
SELECT * FROM Products WHERE HasImages = 1 LIMIT ?, 1
) UNION (
SELECT * FROM Products WHERE HasImages = 1 LIMIT ?, 1
)';
SET #l1 = ROUND(RAND() * #count);
SET #l2 = ROUND(RAND() * #count);
SET #l3 = ROUND(RAND() * #count);
EXECUTE random_records USING #l1
, #l2
, #l3;
DEALLOCATE PREPARE random_records;
It took almost 7 minutes to get the three results. But I'm sure its performance will be much better in your case. Yet if you are looking for a better performance I suggest the following ones as they took less than 30 seconds for me to get the job done (on the same database).
SELECT COUNT(ID)
INTO #count
FROM Products
WHERE HasImages = 1;
PREPARE random_records FROM
'SELECT * FROM Products WHERE HasImages = 1 LIMIT ?, 1';
SET #l1 = ROUND(RAND() * #count);
SET #l2 = ROUND(RAND() * #count);
SET #l3 = ROUND(RAND() * #count);
EXECUTE random_records USING #l1;
EXECUTE random_records USING #l2;
EXECUTE random_records USING #l3;
DEALLOCATE PREPARE random_records;
Bear in mind that both these commands require MySQLi driver in PHP if you want to execute them in one go. And their only difference is that the later one requires calling MySQLi's next_result method to retrieve all three results.
My personal belief is that this is the fastest way to do this.
On the off-chance that you're willing to accept an 'outside the box' type of answer, I'm going to repeat what I said in some of the comments.
The best way to approach your problem is to cache your data in advance (be that in an external JSON or XML file, or in a separate database table, possibly even an in-memory table).
This way you can schedule your performance-hit on the products table to times when you know the server will be quiet, and reduce your worry about creating a performance hit at "random" times when the visitor arrives to your site.
I'm not going to suggest an explicit solution, because there are far too many possibilities on how to build a solution. However, the answer suggested by #ahmed is not silly. If you don't want to create a join in your query, then simply load more of the data that you require into the new table instead.

Categories