I have a query to fetch two values:
string query = #"SELECT price, weight
FROM map
WHERE width = #width AND height = #height LIMIT 1";
if (_connSource.State != ConnectionState.Open)
_connSource.Open();
MySqlCommand cmd = new MySqlCommand(query, _connSource);
cmd.Parameters.AddWithValue("width", width);
cmd.Parameters.AddWithValue("height", height);
r = cmd.ExecuteReader();
if (!r.Read())
{
r.Close();
query = #"SELECT retail_price, 0
FROM globe
WHERE PK_Id = #PK_Id LIMIT 1"
cmd = new MySqlCommand(query, _connSource);
cmd.Parameters.AddWithValue("PK_Id ", 1);
r = cmd.ExecuteReader();
}
What I need is to get price and weight according to a condition, but if it is not present in the table, then I need to get another two fields retail_price, and a constant 0 (doesnt matter what it is) from a totally new table with no constraints from the previous table. Can I get the two in a single query?
Note: Kindly give me optimized queries which doesn't force reading the same values more than once (this function gets executed thousands of times in one single operation, so speed is very critical - a reason why I'm trying to get it in one query). Thanks..
The syntax gets a little ugly, but I think you can do it:
(SELECT price, weight
FROM map
WHERE width = #width AND height = #height LIMIT 1)
UNION ALL
(SELECT retail_price as price, 0 as weight
FROM globe
WHERE PK_Id = #PK_Id LIMIT 1)
This returns 0 to 2 rows. If 2 rows are returned, pick the 1st one.
EDIT You can try avoiding the cost of the second query by using this monstrous construct. I am not sure if MySQL is going to handle it well, but it has a decent chance of avoiding the second query:
select
ifnull(price, (select retail_price from globe where PK_Id = #PK_Id LIMIT 1))
, ifnull(weight, 0)
from map
WHERE width = #width AND height = #height LIMIT 1
If there is no Resultrow to the map WHERE then
the DBNull will be replaced by globe values
SELECT
price = IFNULL(map.price,gl.price),
weight = IFNULL(map.weight,0)
FROM map
LEFT JOIN globe gl ON PK_Id = #PK_Id
WHERE width = #width AND height = #height
LIMIT 1
I did get a pretty hacky solution to this, accidentally. Here is how one can do it:
SELECT price, weight
FROM map
WHERE width = #width AND height = #height LIMIT 1
UNION ALL
SELECT retail_price, 0
FROM globe
WHERE PK_Id = #PK_Id LIMIT 1
The LIMIT 1 clause actually limits it from reading the second value if I get an answer in first select itself. Notice that I haven't added parentheses anywhere so that MySQL doesn't treat this as normal UNION ALL. Read more relevant info here and here
Apparently you can do this to make it more meaningful with this, but I do not think it performs any better:
SELECT price, weight
FROM map
WHERE width = #width AND height = #height LIMIT 1
UNION ALL
(SELECT retail_price, 0
FROM globe
WHERE PK_Id = #PK_Id LIMIT 1) LIMIT 1
Here since I have added brackets, it works like normal UNION ALL, that is I should get both records if possible, but the last LIMIT 1 clause at the end of the query limits the result to the first set.
Since your "limit" clause guarantees a maximum of one row from each query, you could use a left join, and deal with existence of the "map" record using "IF()":
select ifnull(m.price,g.price), ifnull(m.weight,g.weight) from
(SELECT retail_price as price, 0 as weight
FROM globe
WHERE PK_Id = #PK_Id LIMIT 1) g
LEFT JOIN
(SELECT price, weight
FROM map
WHERE width = #width AND height = #height LIMIT 1) m
ON 1=1
The above query assumes that price and weight in the map table cannot be null.
Related
I have the following mysql query that I think should be faster. The database table has 1 million records and the query table 3.5 seconds
set #numberofdayssinceexpiration = 1;
set #today = DATE(now());
set #start_position = (#pagenumber-1)* #pagesize;
SELECT *
FROM (SELECT ad.id,
title,
description,
startson,
expireson,
ad.appuserid UserId,
user.email UserName,
ExpiredCount.totalcount
FROM advertisement ad
LEFT JOIN (SELECT servicetypeid,
Count(*) AS TotalCount
FROM advertisement
WHERE Datediff(#today,expireson) =
#numberofdayssinceexpiration
AND sendreminderafterexpiration = 1
GROUP BY servicetypeid) AS ExpiredCount
ON ExpiredCount.servicetypeid = ad.servicetypeid
LEFT JOIN aspnetusers user
ON user.id = ad.appuserid
WHERE Datediff(#today,expireson) = #numberofdayssinceexpiration
AND sendreminderafterexpiration = 1
ORDER BY ad.id) AS expiredAds
LIMIT 20 offset 1;
Here's the execution plan:
Here are the indexes defined on the table:
I wonder what I am doing wrong.
Thanks for any help
First, I would like to point out some problems. Then I will get into your Question.
LIMIT 20 OFFSET 1 gives you 20 rows starting with the second row.
The lack of an ORDER BY in the outer query may lead to an unpredictable ordering. In particular, the Limit and Offset can pick whatever they want. New versions will actually throw away the ORDER BY in the subquery.
DATEDIFF, being a function, makes that part of the WHERE not 'sargeable'. That is it can't use an INDEX. The usual way (which is sargeable) to compare dates is (assuming expireson is of datatype DATE):
WHERE expireson >= CURDATE() - INTERVAL 1 DAY
Please qualify each column name. With that, I may be able to advise on optimal indexes.
Please provide SHOW CREATE TABLE so that we can see what column(s) are in each index.
I know that there may be a solution elsewhere but everywhere I look, the solutions posted provide no help to me. I know what I call basic SQL but this is the first time coming to this problem and using CASE.
SELECT CASE
WHEN EXISTS (SELECT id FROM map WHERE map="mp" AND showMap="1")
THEN (SELECT file,id FROM map WHERE map="mp" AND showMap="1" ORDER BY date DESC)
ELSE (SELECT file,id FROM map WHERE map="mp" ORDER BY date ASC LIMIT 1)
END
By what I have found so far, Error: ER_OPERAND_COLUMNS: Operand should contain 1 column(s) means that I can only select one item instead of the two that I want. How can I change this code to let me select both the id and file from the map that I want.
To explain the code more, if map mp has a version that has showMap=1 then select all of those maps, else if map mp does not have a map with showMap=1 then select the 1st map
There will always be at least one map mp.
You can only select one column at a time from the data.
I am thinking this does what you want:
SELECT file, id
FROM map
WHERE map = 'mp'
ORDER BY (showMap = '1') DESC,
date ASC
LIMIT 1;
This prioritizes the values using ORDER BY, putting the row(s) (if any) with showMap = '1' first and then ordering by date. The query returns the first row.
EDIT:
If you want all the maps with showMap = '1', then something a bit more complex:
(SELECT file, id
FROM map
WHERE map = 'mp' AND showMap = '1'
) UNION ALL
(SELECT file, id
FROM map
WHERE map = 'mp' AND
NOT EXISTS (SELECT 1
FROM map m2
WHERE m2.map = 'mp' and m2.showMap = '1'
)
ORDER BY date ASC
LIMIT 1
);
Let's assume I have the following tables:
items table
item_id|view_count
item_views table
view_id|item_id|ip_address|last_view
What I would like to do is:
If last view of item with given item_id by given ip_address was 1+ hour ago I would like to increment view_count of item in items table. And as a result get the view count of item. How I will do it normally:
q = SELECT count(*) FROM item_views WHERE item_id='item_id' AND ip_address='some_ip' AND last_view < current_time-60*60
if(q==1) then q = UPDATE items SET view_count = view_count+1 WHERE item_id='item_id'
//and finally get view_count of item
q = SELECT view_count FROM items WHERE item_id='item_id'
Here I used 3 SQL queries. How can I merge it into one SQL query? And how can it affect the processing time? Will it be faster or slower than previous method?
I don't think your logic is correct for what you describe that you want. The query:
SELECT count(*)
FROM item_views
WHERE item_id='item_id' AND
ip_address='some_ip' AND
last_view < current_time-60*60
is counting the number of views longer ago than your time frame. I think you want:
last_view > current_time-60*60
and then have if q = 0 on the next line.
MySQL is pretty good with the performance of not exists, so the following should work well:
update items
set view_count = view_count+1
WHERE item_id='item_id' and
not exists (select 1
from item_views
where item_id='item_id' AND
ip_address='some_ip' AND
last_view > current_time-60*60
)
It will work much better with an index on item_views(item_id, ip_address, last_view) and an index on item(item_id).
In MySQL scripting, you could then write:
. . .
set view_count = (#q := view_count+1)
. . .
This would also give you the variable you are looking for.
update target
set target.view_count = target.view_count + 1
from items target
inner join (
select item_id
from item_views
where item_id = 'item_id'
and ip_address = 'some_ip'
and last_view < current_time - 60*60
) ref
on ref.item_id = target.item_id;
You can only combine the update statement with the condition using a join as in the above example; but you'll still need a separate select statement.
It may be slower on very large set and/or unindexed table.
I'm trying to limit the number of elements in my result set by setting a limit to my sql logic. I have two seperate functions to achieve what I want. The first one has a limit I've set manually e.g 0, X. The second function has two extra arguments that is min and max and these are set as the limit. But when the min and max are e.g 7, 14 it gives me more elements then 7. There are no duplications in the result set since I have unique id's on each element and they check out. Also the integers passed to the sql function have the correct intervall.
What am I doing wrong?
"SELECT table1.*, table2.user_id FROM table1 LEFT JOIN table2 ON table1.col1 = table2.col2
WHERE table1.col1 = :param1 AND table1.col2 = 1 AND table1.col3 = 0 ORDER BY table1.col4 DESC LIMIT $min, $max";
The syntax is SELECT Syntax is not
limit min, max
but
limit offset, row_count
so, limit 7, 14 says retrieve 14 rows at offset 7.
I have a simple table of data, and I'd like to select the row that's at about the 40th percentile from the query.
I can do this right now by first querying to find the number of rows and then running another query that sorts and selects the nth row:
select count(*) as `total` from mydata;
which may return something like 93, 93*0.4 = 37
select * from mydata order by `field` asc limit 37,1;
Can I combine these two queries into a single query?
This will give you approximately the 40th percentile, it returns the row where 40% of rows are less than it. It sorts rows by how far they are from the 40th percentile, since no row may fall exactly on the 40th percentile.
SELECT m1.field, m1.otherfield, count(m2.field)
FROM mydata m1 INNER JOIN mydata m2 ON m2.field<m1.field
GROUP BY
m1.field,m1.otherfield
ORDER BY
ABS(0.4-(count(m2.field)/(select count(*) from mydata)))
LIMIT 1
As an exercise in futility (your current solition would probably be faster and prefered), if the table is MYISAM (or you can live with the approximation of InnoDB):
SET #row =0;
SELECT x.*
FROM information_schema.tables
JOIN (
SELECT #row := #row+1 as 'row',mydata.*
FROM mydata
ORDER BY field ASC
) x
ON x.row = round(information_schema.tables.table_rows * 0.4)
WHERE information_schema.tables.table_schema = database()
AND information_schema.tables.table_name = 'mydata';
There's also this solution, which uses a monster string made by GROUP_CONCAT. I had to up the max on the output like so to get it to work:
SET SESSION group_concat_max_len = 1000000;
MySql wizards out there: feel free to comment on the relative performance of the methods.