Need MySQL Query Guru (intersection of record data) - mysql

Say i have a table as shown:
id, auctionUser, auctionId, MinPrice, NumBids, PlacedBids
And then say i've got the following entries in above table that have the same auctionId:
1 | user1 | 99 | 10.25 | 20 | 0
2 | user2 | 99 | 10.50 | 50 | 0
Is there a way to write a query ( WHERE auctionId = 99 ) that would return a row for every 0.01 of MinPrice where the two rows would 'intersect' (don't know if that's the right word but it's the best i could come up with to describe it) based on the number of bids in NumBids? So for the data above, there would be an 'intersect' of the two users from 10.50 thru 10.75. I'd like to be able to create the flowing data to display like so , alternating bids between the users for the number of bids set in NumBids:
(bidAmount) | (auctionUser) | NumBids | PlacedBids
10.50 | user2 | 50 | 1
10.51 | user1 | 20 | 1
10.52 | user2 | 50 | 2
10.53 | user1 | 20 | 2
10.54 | user2 | 50 | 3
10.55 | user1 | 20 | 3
.
.
.
10.70 | user2 | 50 | 20
10.71 | user1 | 20 | 20 <-- ends here for user1 since 20 NumBids would be used up
10.72 | user2 | 50 | 21
I don't even know if this is possible via a sql query or not -- or even how to start such a query. I thought i'd throw it out there to see if any sql guru's had and idea. I figured if there was a way to do it, it would probably be much faster to produce it from a query that trying to use php to cycle through and produce the result...maybe not though.
As always, MUCHO THANKS for any time and advice you can spare on this!

I can't say I understand exactly what you want, but I think you need to generate rows. One way of generating rows is to use a Numbers table, which is basically a table of consecutive integers.
Have a look at my answer to this question. It is not related to your question, but there is code to generate such a numbers table.
So if you want to generate 1 row for each 0,01 difference, you would calculate nr of cents (or whatever the currency was) and join to the numbers table with a filter on n < nr_of_cents.
Edit:
Ok, I'll try. First, some sample data.
create table auctions(
auctionuser int
,auctionid int
,minprice decimal(5,2)
,numbids int
);
insert into auctions values(1, 1, 2.20, 2);
insert into auctions values(2, 1, 3.30, 4);
insert into auctions values(3, 1, 4.40, 6);
select *
from auctions
where auctionid = 1;
+-------------+-----------+----------+---------+
| auctionuser | auctionid | minprice | numbids |
+-------------+-----------+----------+---------+
| 1 | 1 | 2.20 | 2 |
| 2 | 1 | 3.30 | 4 |
| 3 | 1 | 4.40 | 6 |
+-------------+-----------+----------+---------+
3 rows in set (0.00 sec)
I think the following is close to what you want. Note that I have used the numbers table in the post I linked to.
select a.auctionuser
,n as user_bid
,minprice
,numbids
,a.minprice + (0.01 * (n-1)) as bid
from auctions a
,numbers
where numbers.n <= a.numbids
and a.auctionid = 1
order
by n
,a.minprice
,a.auctionuser;
+-------------+----------+----------+---------+------+
| auctionuser | user_bid | minprice | numbids | bid |
+-------------+----------+----------+---------+------+
| 1 | 1 | 2.20 | 2 | 2.20 |
| 2 | 1 | 3.30 | 4 | 3.30 |
| 3 | 1 | 4.40 | 6 | 4.40 |
| 1 | 2 | 2.20 | 2 | 2.21 |
| 2 | 2 | 3.30 | 4 | 3.31 |
| 3 | 2 | 4.40 | 6 | 4.41 |
| 2 | 3 | 3.30 | 4 | 3.32 |
| 3 | 3 | 4.40 | 6 | 4.42 |
| 2 | 4 | 3.30 | 4 | 3.33 |
| 3 | 4 | 4.40 | 6 | 4.43 |
| 3 | 5 | 4.40 | 6 | 4.44 |
| 3 | 6 | 4.40 | 6 | 4.45 |
+-------------+----------+----------+---------+------+
12 rows in set (0.00 sec)

Related

Precalculate numbers of records for each possible combination

I have a mySQL database table containing cellphones information like this:
ID Brand Model Price Type Size
==== ===== ===== ===== ====== ====
1 Apple A71 3128 A 40
2 Samsung B7C 3128 B 20
3 Apple ZX5 3128 A 30
4 Huawei Q32 2574 B 40
5 Apple A21 2574 A 25
6 Apple A71 3369 A 30
7 Samsung A71 7413 C 40
Now I want to create another table, that would contain counts for every possible combination of the parameters.
Params Count
============================================== =======
ALL 1000000
Brand(Apple) 20000
Brand(Apple,Samsung) 40000
Brand(Apple),Model(A71) 7100
Brand(Apple),Type(A) 6000
Brand(Apple),Model(A71,B7C),Type(A,B) 7
Model(A71) 12514
Model(A71,B7C) 26584
Model(A71),Type(A) 6521
Model(A71),Type(A,B) 8958
Model(A71),Type(A,B),Size(40) 85
And so on for every possible combination. I was thinking about creating a stored procedure (that i would execute periodically), that would perform queries with every existing condition like that, but I am a little stuck on how exactly should it look like. Or is there a better way how to do this?
Edit: the reason why I want to store information like this is to be able to show number of results in filter in client application, like in the picture.
I would like to create index on the Params column to be able to get the Count number for given hash instantly, improving performance.
I also tried querying and caching the values dynamically, but I want to try this approach as well, so I can compare which one is more effective.
This is how I am calculating the counts now:
SELECT COUNT(*) FROM products;
SELECT COUNT(*) FROM products WHERE Brand IN ('Apple');
SELECT COUNT(*) FROM products WHERE Brand IN ('Apple', 'Samsung');
SELECT COUNT(*) FROM products WHERE Brand IN ('Apple') AND Model IN ('A71');
etc.
You can use a ROLLUP for this.
SELECT
model, type, size, COUNT(*)
FROM mytab
GROUP BY 1, 2, 3
WITH ROLLUP
With your sample data, we get the following:
| model | type | size | COUNT(*) |
| ----- | ---- | ---- | -------- |
| A21 | A | 25 | 1 |
| A21 | A | | 1 |
| A21 | | | 1 |
| A71 | A | 30 | 1 |
| A71 | A | 40 | 1 |
| A71 | A | | 2 |
| A71 | C | 40 | 1 |
| A71 | C | | 1 |
| A71 | | | 3 |
| B7C | B | 20 | 1 |
| B7C | B | | 1 |
| B7C | | | 1 |
| Q32 | B | 40 | 1 |
| Q32 | B | | 1 |
| Q32 | | | 1 |
| ZX5 | A | 30 | 1 |
| ZX5 | A | | 1 |
| ZX5 | | | 1 |
| | | | 7 |
The subtotals are present in the rows with null values in different columns, and the total is the last row where all group by columns are null.

MySQL, Insert Row in-between two others

I'm coding a website for a photographer and I'm currently working on gallery implimentation.
I need to be able to take a row from point n.a and move it to point n.b
Here's an example of the raw table:
|gallery_img |
|--------------------------|
| id | fk_gal | fk_img | o |
| | | | |
| 0 | 16 | 240 | 1 |
| 1 | 16 | 322 | 2 |
| 2 | 27 | 240 | 1 |
| 3 | 16 | 245 | 3 |
| 4 | 16 | 210 | 4 |
| 5 | 27 | 530 | 2 |
All fields are INT(11). 'id' Auto_increments. 'fk_gal' and 'fk_img' are linked to other, irrelevant, tables via FOREIGN_KEY.
Now, 'o' is the field I'm focusing on. It determines what order the images will be displayed on the website. This value needs to always be unique for each table. To clarify, If I only call one table, 'o' should be different in every row. However, if I call the entire table, 'o[0]' might reoccur a few times.
So here's what I need. Firstly, I'm only going to be running this function on only one gallery at a time so all visuals of the table from here on out are going to be filtered with 'SELECT * FROM gallery_img WHERE fk_gal = 16'.
I need to change 'o' from n to n2 which will effectively move it on the database.
|gallery_img |
|--------------------------|
| id | fk_gal | fk_img | o |
| | | | |
| 0 | 16 | 240 | 1 |
| | | | | <--
| 1 | 16 | 322 | 2 |+ |
| 3 | 16 | 245 | 3 |+ |
| 4 | 16 | 210 | 4 | --|
The code needs to move the desired row (in this example 'o=4') to 1 and simultaneously move all of the next rows down to prevent any reoccurrences.
Here's my code I have right now. I'm coding my MySql scripts via PHP.
I am using the $n variable here. It includes the following data:
$n = array(gallery_id,img_id,target_o);
sql("UPDATE gallery_img SET o = o + 1 ORDER BY o ASC LIMIT ". ($n[2] - 1) .", 18446744073709551615;");
sql("UPDATE gallery_img SET o = ". ($n[2] + 2) ." WHERE fk_img = $n[1] AND fk_gal = $n[0];");
The problem I'm having with this is that when I execute it I get one of these two outputs:
|gallery_img |
|--------------------------|
| id | fk_gal | fk_img | o |
| | | | |
| 0 | 16 | 240 | 1 |
| 4 | 16 | 210 | 1 | <-- Shouldn't be duplicate
| 1 | 16 | 322 | 2 |
| 3 | 16 | 245 | 3 |
|gallery_img |
|--------------------------|
| id | fk_gal | fk_img | o |
| | | | |
| 0 | 16 | 240 | 1 |
| 4 | 16 | 210 | 2 |
| 1 | 16 | 322 | 4 |-|
| 3 | 16 | 245 | 4 | |-- Shouldn't be duplicate
| 5 | 16 | 273 | 4 | |
| 6 | 16 | 14 | 4 |-|
A good way to think of it is as so:
UPDATE
If you have any questions please let me know!
Thanks ahead of time for your help!
So if you're wanting to change the row WHERE o=4 to o=1 then increment the number to be replaced and all greater numbers.
UPDATE gallery_img SET o = (o + 1) WHERE o >= 1
Then update the row that you want to be o=1:
UPDATE gallery_img SET o = 1 WHERE fk_img = something1 AND fk_gal = something2
Or if you only know the o use o=(4+1) since it changed in the last UPDATE:
UPDATE gallery_img SET o = 1 WHERE o = 5
Can I suggest a hack? For the column o don't use an integer number, but a DOUBLE PRECISION one.
It would be much easier to insert a row in between, just by averaging the values of the previous and next row. If you need to insert between 3 and 4, you can just insert a row with 3.5.
Of course, after some time (after 50 times at least) you would like to re-number those values, since a DOUBLE PRECISION has 53 bits for the mantissa.

return a unique list from query result after removing duplicate rows from the table

I have two columns product_id, r_store_id which have a few rows with same values. Rest of the column rows have different values
I have duplicate rows with same r_store_id and product_id because every time I have to add new entries into this table. I want unique rows list with latest update_dt
(refer the DB table below).
id | m_store_id |r_store_id|product_id | amount |update_dt |
1 | 4 | 1 | 45 | 10 |18/03/5 |
2 | 4 | 1 | 45 | 100 |18/03/9 |
3 | 4 | 1 | 45 | 20 |18/03/4 |
4 | 5 | 2 | 49 | 10 |18/03/8 |
5 | 5 | 2 | 49 | 60 |18/03/2 |
6 | 9 | 3 | 45 | 19 |18/03/5 |
7 | 9 | 3 | 45 | 56 |18/03/3 |
My result should look like this:
id | m_store_id |r_store_id|product_id | amount |update_dt |
2 | 7 | 1 | 45 | 100 |18/03/9 |
4 | 5 | 2 | 49 | 10 |18/03/8 |
6 | 9 | 3 | 45 | 19 |18/03/5 |
I want to put this result in a list like this:
List<Sales> salesList = (List<Sales>) query.list();
I am not able to find an easy solution. Please help me with this!
We can select the chronologically most recent update for each store, and then join to get all the variables:
select a.*
from mytable a
join (select m_store_id, r_store_id, product_id, max(update_dt) as maxdate
from mytable
group by 1,2,3) b
on a.m_store_id=b.m_store_id
and a.r_store_id=b.r_store_id
and a.product_id=b.product_id
and a.update_dt = b.maxdate;

How to get this specific user rankings query in mysql?

I've got tbl_items in my user database that I want to sort user rankings on a particular item with certain id (514). I have test data on my dev environment with this set of data:
mysql> select * from tbl_items where classid=514;
+---------+---------+----------+
| ownerId | classId | quantity |
+---------+---------+----------+
| 1 | 514 | 3 |
| 2 | 514 | 5 |
| 3 | 514 | 11 |
| 4 | 514 | 46 |
| 5 | 514 | 57 |
| 6 | 514 | 6 |
| 7 | 514 | 3 |
| 8 | 514 | 27 |
| 10 | 514 | 2 |
| 11 | 514 | 73 |
| 12 | 514 | 18 |
| 13 | 514 | 31 |
+---------+---------+----------+
12 rows in set (0.00 sec)
so far so good :) I wrote the following query:
set #row=0;
select a.*, #row:=#row+1 as rank
from (select a.ownerid,a.quantity from tbl_items a
where a.classid=514) a order by quantity desc;
+---------+----------+------+
| ownerid | quantity | rank |
+---------+----------+------+
| 11 | 73 | 1 |
| 5 | 57 | 2 |
| 4 | 46 | 3 |
| 13 | 31 | 4 |
| 8 | 27 | 5 |
| 12 | 18 | 6 |
| 3 | 11 | 7 |
| 6 | 6 | 8 |
| 2 | 5 | 9 |
| 7 | 3 | 10 |
| 1 | 3 | 11 |
| 10 | 2 | 12 |
+---------+----------+------+
12 rows in set (0.00 sec)
that ranks correctly the users. However in a table with lots of records, I need to do the following:
1) be able to get small portion of the list, around where the user ranking actually resides, something that would get me the surrounding records, preserving the overall rank:
I tried to do these things with setting a user variable to the ranking of the current user and by using offset and limit, but couldn't preserve the overall ranking.
This should get me something like the following (for instance ownerId=2 and surroundings limit 5:
+---------+----------+------+
| ownerid | quantity | rank |
+---------+----------+------+
| 3 | 11 | 7 |
| 6 | 6 | 8 |
| 2 | 5 | 9 | --> ownerId=2
| 7 | 3 | 10 |
| 1 | 3 | 11 |
+---------+----------+------+
5 rows in set (0.00 sec)
2) I'd also need another query (preferably single query) that gets me the top 3 places + the ranking of particular user with certain id, preferably with a single query, no matter if he's among the top 3 places or not. I couldn't get this as well
It would look like the following (for instance ownerId=2 again):
+---------+----------+------+
| ownerid | quantity | rank |
+---------+----------+------+
| 11 | 73 | 1 |
| 5 | 57 | 2 |
| 4 | 46 | 3 |
| 2 | 5 | 9 | --> ownerId=2
+---------+----------+------+
4 rows in set (0.00 sec)
Also I'm in a bit of a concern about the performance of the queries on a table with millions of records...
Hope someone helps :)
1) 5 entries around a given id.
set #row=0;
set #rk2=-1;
set #id=2;
select b.* from (
select a.*, #row:=#row+1 as rank, if(a.ownerid=#id, #rk2:=#row, -1) as rank2
from (
select a.ownerid,a.quantity
from tbl_items a
where a.classid=514) a
order by quantity desc) b
where b.rank > #rk2 - 3
limit 5;
Though you'll get an extra column rank2: you probably want to filter it out by explicit list of columns instead of b.*. Maybe it's possible whith a having clause rather than an extra nesting.
2) 3 top ranked entries + 1 specific id
select b.* from (
select a.*, #row:=#row+1 as rank
from (
select a.ownerid,a.quantity
from tbl_items a
where a.classid=514) a
order by quantity desc) b
where b.rank < 4 or b.ownerid=#id

What happens if I select two tables with no WHERE clause?

I had a technical interview last week, and my interviewer asked me what happens if I run the following query:
SELECT * FROM tbl1, tbl2
I think I answered it correctly, but it wasn't an in-depth answer.
I said that I would select all the columns in both tables. For example if tbl1 has 3 columns, and tbl2 has 4 columns. The result set would have 7 columns.
Then he asked me why 7? and I said because I was selecting everything from each table.
That was a bad answer, but I couldn't think of anything else.
To cut to the chase, after the interviewed I executed the latter statement using two tables.
Table A, had 3 animal: dog, cat and elephant.
Table B had 2 names: Mat and Beth
This is the result set that I got after the statement being executed:
*********************************************
| id_tbl1 | name_tbl1 | id_tbl2 | name_tbl2 |
*********************************************
| 1 | dog | 1 | Mat |
| 2 | cat | 1 | Mat |
| 3 | elephant | 1 | Mat |
| 1 | dog | 2 | Beth |
| 2 | cat | 2 | Beth |
| 3 | elephant | 2 | Beth |
*********************************************
So my question is, why does the statement behaves like that?
In other words:
Why does the Table B's records repeat themselves until I reach the end of table A, and then it starts all over again?
How would you have answered the question in a way that it would've "WOW'd" the interviewer?
If this question does not belong to SO, feel free to delete it or close it!
If you do a select like this, all rows in one resultset are joined to all rows in the other resultset (Cartesian Product).
So you get a list of all rows of the first table with the first row of the second table, Then all entries for the second row and so on. The order may be an implementation detail. Not sure if it is defined that the first order is by the first table, it might be different across implementations.
If you join three tables (or more), then the same happens with all rows of all tables. This, of course, is not only for tables, but for any result set from joins.
The result will be a cartisian product
take a look at this example
SQL Example
You can see there are two tables one has 5 records and the other has 4 and the result is 20 records. Means 5 * 4 = 20 instead of 5 + 4 = 9 as you are assuming.
Table1
| IDX | VAL |
---------------
| 1 | 1val1 |
| 1 | 1val2 |
| 2 | 2val1 |
| 2 | 2val2 |
| 2 | 2val3 |
Table2
| ID | POINTS |
---------------
| 1 | 2 |
| 2 | 10 |
| 3 | 21 |
| 4 | 29 |
Result of below query
SELECT * FROM Table1 , Table2
| IDX | VAL | ID | POINTS |
-----------------------------
| 1 | 1val1 | 1 | 2 |
| 1 | 1val1 | 2 | 10 |
| 1 | 1val1 | 3 | 21 |
| 1 | 1val1 | 4 | 29 |
| 1 | 1val2 | 1 | 2 |
| 1 | 1val2 | 2 | 10 |
| 1 | 1val2 | 3 | 21 |
| 1 | 1val2 | 4 | 29 |
| 2 | 2val1 | 1 | 2 |
| 2 | 2val1 | 2 | 10 |
| 2 | 2val1 | 3 | 21 |
| 2 | 2val1 | 4 | 29 |
| 2 | 2val2 | 1 | 2 |
| 2 | 2val2 | 2 | 10 |
| 2 | 2val2 | 3 | 21 |
| 2 | 2val2 | 4 | 29 |
| 2 | 2val3 | 1 | 2 |
| 2 | 2val3 | 2 | 10 |
| 2 | 2val3 | 3 | 21 |
| 2 | 2val3 | 4 | 29 |
I think you are confusing yourself by running an example with two tables that have identical fields. You are referring to a Union, which will combine the values of 1 table with another, and using your example this would give you 3 + 4 = 7 results.
The comma separated FROM statement is doing JOIN, which will go through all values in Table X and pair them with all the values of Table Y. This would result in Size of X * Size of Y results, and using your example this would be 3 * 4 = 12.