Multiple SELECTs in a complex mysql statement - mysql

I made a mysql query that is getting a result set from table A using a value 'x' stored in table B. Right now whenever I need 'x' I need to reSELECT it up to 6 times.
Simplified version of my query looks something like this:
SELECT * FROM A WHERE a = (SELECT x FROM B ...) AND b = (SELECT x FROM B ...)-5 AND c = (SELECT x FROM B ...)+7
Is there a way to store that value 'x' during the evaluation of my sql statement?
If not, does it make sense to use 2 queries instead (first one to get 'x' and second one with 'x' included in the real statement)?
If you want to see one of my statements, here is an example:
"(SELECT * FROM map WHERE x BETWEEN :x-:sight and :x+:sight AND y BETWEEN :y-:sight+:map_max_y and :map_max_y) /*1.part*/
UNION
(SELECT * FROM map WHERE x BETWEEN :map_min_x and MOD((:x+:sight),:map_max_x) AND y BETWEEN :y-:sight+:map_max_y and :map_max_y ORDER BY y LIMIT 225) /*2.part*/
UNION
(SELECT * FROM map WHERE x BETWEEN :x-:sight and :map_max_x AND y BETWEEN :map_min_y and :y+:sight ORDER BY y LIMIT 225) /*3.part*/
UNION
(SELECT * FROM map WHERE x BETWEEN :map_min_x and MOD((:x+:sight),:map_max_x) AND y BETWEEN :map_min_y and :y+:sight ORDER BY y LIMIT 225) /*4.part*/";
'x', 'y', 'sight' are all stored in the DB and it hurts my eyes to reload them that many times (this is the older version which doesn't load them from DB just yet). And I believe making 2 queries is a bad habit aswell.
Thank you in advance, guys :)

Mysql optimizer should be able to detect such duplicated subselect and perform it only once. Just make sure it is really the same select which does not use anything variable from outside, and use EXPLAIN to confirm, that it gets optimized.

Related

Rails select top n records per group (memory leak)

I have this method that using find_by_sql which is return 10 latest records of each source
def latest_results
Entry.find_by_sql(["
select x.id,x.created_at,x.updated_at,x.source_id,x.`data`,x.`uuid`,x.source_entry_id
from
(select t.*,
(#num:=if(#group = `source_id`, #num +1, if(#group := `source_id`, 1, 1))) row_number
from (
select d.id,d.created_at,d.updated_at,d.source_id,d.`data`,d.`uuid`,d.source_entry_id
from `streams` a
JOIN `stream_filters` b
on b.stream_id=a.id
JOIN `filter_results` c
on c.filter_id=b.id
JOIN `entries` d
on d.id=c.entry_id
where a.id=?
) t
order by `source_id`,created_at desc
) as x
where x.row_number <= 10
ORDER BY x.created_at DESC
",self.id])
end
It's working properly on local environment with limited records.
I have t2.micro which has 2 Gib memory to serving the application. Now this query running out my whole memory and app get frizzing.
any suggestion how can I do it better ? I want to solve this without increasing the size of machine.
I had a similar problem once. The solution with mysql variables seems neat at the first place, though it is hard to optimize. It seems that is doing a full table scan in your case.
I would recommend to fetch the sources you want to display first. And then run a second query with multiple top 10 selects, one per source, all combined with a union.
The union top 10 select will have some repetive statements which you can easily autogenerate with ruby.
# pseudo code
sources = Entry.group(:source).limit(n)
sql = sources.map do |source|
"select * from entries where source = #{source} order by created_at limit 10"
end.join("\nunion all\n")
Entry.find_by_sql(sql)

Count with a subselect yielding double the amount

I'm new to SQL.
Problem: Say if I were to count the amount that is contained in the alias table of "x" COUNT(x.xValue) to be 217. Now when I add the sub-query "y" and then do the count again, I have the COUNT(x.xValue) to suddenly square its self -> 47089. Why is this happening?
(Note: Both alias tables "x" and "y" have the same amount -> 217.)
How do I fix this problem. I don't want to use Variables or Views.
SELECT COUNT(x.xValue) + COUNT(y.yValue) AS CountXY
FROM
(SELECT value AS xValue FROM table1
WHERE
...) AS x,
(SELECT value AS yValue FROM table1
WHERE
...) AS y
Result of 'CountXY' : 94178.
Result I'm expecting 'CountXY' : 434
The problem is that you are doing two sub-queries and then trying to call the values return directly.
This will behave as selecting one value from table x and matching it to every single value in table y. This obviously creates the squared return effect.
What you need to use is the JOIN to combine both data-sets so that you get the 1 to 1 relationship you are trying to achieve.
This is how the above should be done with your previous sub-query:
SELECT COUNT(A.value) AS x, COUNT(B.value) AS y
FROM table1 AS A
JOIN table1 AS B
ON A.attr1 = B.attr1
AND A.attr2 = B.attr2
WHERE B.attr1 != 'whatever'
AND B.attr2 = 'whatever'
AND A.attr3 = 'something'
AND B.attr3 = 'something different'
The above query should return the correct 1 to 1 relationship you are looking for. Replacing your sub-query with the one above should give you the correct answer

C# - Mysql . Choose random with subcondition

I have a table with the columns :
pos1 pos2 pos3 pos4 pos5 id
pos1-5 -> varchar , id -> int
I'm using the query:
Query = "select distinct * from (SELECT * FROM database.turkey2 t1 WHERE pos2='"+comboBox2.Text+"' and NOT EXISTS (SELECT 1 FROM database.turkey2used t2 WHERE t1.id = t2.id)) as t3 order by rand() limit "+amount+" ;";
Meaning the user will choose pos2(will make it static) while the rest is random , the amount will be set by the user as well.
What I'm trying to do is to add a condition in this query that force at least 2 diffrent pos(positions) that must be chosen in random.
meaning , I don't want to get 2 or more rows with the same valuse at pos1,2,3,4 and only pos5 will be diffrent(igonore the id - and the rule dosen't apply only to pose 5 of course).
I solved the problem by runnning another query on the already select amount - but that's not good because if the user asked for 200 combinitions then after the "fix" he may lose some rows.
some other info - it's a medium size db (about 10m rows).
Please keep the solution simple because I'm rather new to mysql and c#.
Thanks.

selecting data from 2 tables where multiple columns and rows intersect with 'X'

its tricky to explain what i am trying to do so here goes
I have 2 tables derived from a single excel spreadsheet. the spreadsheet defines causes and effects.It has 3 points of reference so i cant make it into 1 table
this spreadsheet has a list of events down the left and effects accross the top. these are associated with each other by inserting an X in the row/column matching the event to the effect. So the intersecting columns/rows form a matrix. So for eg an event could be. 'turn on lightswitch'. follow this row along untill 'X' is found. then follow column up to effects and it shows 'light turns on' (its a little more complicated than that in reality) there can be multiple effects defined by X's in the same row
My idea was in the where statement to use table1.* and table2.* but I was looking for a wildcard to search rows/columns but after research this is not possible in mysql. Using OR for all the furter row/column combinations wont work because it just shows everything with an X in
I am a bit stumped how to query the X row/column part of the table so only the 'example' result part of the query is displayed after searching all the X's A single query below is successful but i need to search table1.1-60 table2.2-61 for occurrances of 'X' by only changeing the '%example%' part of the query in a webpage mysql/php type form and then displaying the results on another page
any suggestions/alternatives welcome thanks
SELECT
table1.equipment,
table1.tagno,
table2.equipment,
table2.action,
table2.service,
table2.tagno
FROM
table1 ,
table2
WHERE
table1.tagno LIKE '%example%' AND
table1.1 = 'X' AND
table2.2 = 'X'
There is no natural way to join column N from table1 with column N+1 in table2; you'll have to specify the joins "by hand" (or in a program, both of which will be ugly).
You want:
select * from table1 t1, table2 t2
where t1.tagno like '%example%' AND t1.1 ='X' and t2.2 = 'X'
UNION
select * from table1 t1, table2 t2
where t1.tagno like '%example%' AND t1.2 ='X' and t2.3 = 'X'
UNION
-- ...
This could be very inefficient. As suggested in the comments, remodelling the data would give you cleaner queries and a happier future.
To give more insight into my suggestion, I would re-model the data like so:
CREATE TABLE effects (
id INT,
name VARCHAR(100)
);
CREATE TABLE events (
id INT,
name VARCHAR(100)
);
CREATE TABLE effects_events (
effect_id INT,
event_id INT
);
effects_events is a Junction Table
So you could query it like this:
SELECT * FROM events WHERE events.id = effects_events.event_id AND effects_events.effect_id = effects.id AND effects.name = 'light turns on';
Or:
SELECT * FROM effects WHERE effects.id = effects_events.effect_id AND effects_events.event_id = events.id AND events.name = 'turn on lightswitch';
I might be misunderstanding something in your question but this seems like the most straightforward solution to me.

Producing multiple maximum and minimum values with SQL Query

I'm becoming frustrated with a curious limitation of SQL - its apparent inability to relate one record to another outside of aggregate functions. My problem is summarized thusly.
I have a table, already sorted. I need to find its maximum values (note the plural!) and minimum values. No, I am not looking for a single maximum or single minimum. More specifically I'm trying to generate a list of the local peaks of a numeric sequence. A rough description of an algorithm to generate this is:
WHILE NOT END_OF_TABLE
IF RECORD != FIRST_RECORD AND RECORD != LAST_RECORD THEN
IF ((RECORD(Field)<RECORD_PREVIOUS(Field) AND RECORD(Field)<RECORD_NEXT(Field)) OR
RECORD(Field)>RECORD_PREVIOUS(Field) AND RECORD(Field)>RECORD_NEXT(Field)) THEN
ADD_RESULT RECORD
END IF
END IF
END WHILE
See the Problem? I need to do a query that a given record must compare against the previous and next records' values. Can this even be accomplished in standard SQL?
Your frustration is shared by many; while SQL is great for working with general sets, it's terribly deficient when trying to work with issues specific to ordered sets (whether it's physically ordered in the table or there is an implicit or explicit logical order is irrelevant). There are some things that can help (for example, the rank() and row_number() functions), but the solutions can differ across RDBMS's.
If you can be specific about which platform you're working with, I or someone else can provide a more detailed answer.
You have to self-join twice and generate a rownumber without gaps:
In T-SQL:
WITH ordered AS (
SELECT ROW_NUMBER() OVER (ORDER BY your_sort_order) AS RowNumber
,* -- other columns here
)
SELECT *
FROM ordered
LEFT JOIN ordered AS prev
ON prev.RowNumber = ordered.RowNumber - 1
LEFT JOIN ordered AS next
ON next.RowNumber = ordered.RowNumber + 1
WHERE -- here you put in your local min/local max and end-point handling logic - end points will have NULL in next/prev
Yes. You need a self join - but without a database schema, it's hard to be specific about the solution.
Specifically, I'm wondering about the "ordering" thing you mention - but I'm going to assume there's an "ID" field we can use for this.
(Oh, and I'm using old-school join syntax, coz I'm a dinosaur).
select *
from myTable main,
myTable previous,
myTable next
where previous.id = main.id - 1
and next.id = main.id + 1
and previous.record > main.record
and next.record < main.record
(I think I've interpreted your requirement correctly in the greater/less than clauses, but adjust to taste).
SELECT
current.RowID,
current.Value,
CASE WHEN
(
(current.Value < COALESCE(previous.Value, current.Value + 1))
AND
(current.Value < COALESCE(subsequent.Value, current.Value + 1))
)
THEN
'Minima'
ELSE
'Maxima'
END
FROM
myTable current
LEFT JOIN
myTable previous
ON previous.RowID = (SELECT MAX(RowID) FROM myTable WHERE RowID < current.ROWID)
LEFT JOIN
myTable subsequent
ON subsequent.RowID = (SELECT MIN(RowID) FROM myTable WHERE RowID > current.ROWID)
WHERE
(
(current.Value < COALESCE(previous.Value, current.Value + 1))
AND
(current.Value < COALESCE(subsequent.Value, current.Value + 1))
)
OR
(
(current.Value > COALESCE(previous.Value, current.Value - 1))
AND
(current.Value > COALESCE(subsequent.Value, current.Value - 1))
)
Note: The < and > logic is copied from you, but does not cater for local maxima/minima that are equal across one or more consecutive records.
Note: I've created a fictional RowID to join the records in order, all the is important is that the joins get the "previous" and "subsequent" records.
Note: The LEFT JOINs and COALESCE statements cause the first and last values to always be counted as a maxima or minima.