I'm trying to condense data that I have in my database into rows with their points tallied to see the most popular.
If I had a data table like:
`data`
item1 item2
1
1 2
1 3
1 3
2 3
And wanted the condensed version to be:
`data_sum`
item1 item2 Tally
1 2 2
1 3 3
2 3 1
How would I achieve this? I have somewhat of an idea here:
$popdata = mysql_query("SELECT * FROM data");
while($add = #mysql_fetch_array($popdata)){
$qitem1 = "SELECT * FROM data_sum WHERE item1='".$add['item1']."'";
$ritem1 = mysql_query($qitem1);
if(mysql_num_rows($ritem1) > 0){
$qitem2 = "SELECT * FROM data_sum WHERE item2='".$add['item2']."'";
$ritem2 = mysql_query($qitem2);
if (mysql_num_rows($ritem2) > 0){
$sql = "UPDATE Tally=Tally + 1 WHERE item1='".$add['item1']."' AND item2='".$add['item2']."'";
$update = mysql_query($sql);
}
else{
$sql = "INSERT INTO data_sum (item1, item2) VALUES('$item1', '$item2')";
$insert = mysql_query($sql);
}
else{
$sql = "INSERT INTO data_sum (item1, item2) VALUES('$item1', '$item2')";
$insert = mysql_query($sql);
}
Yes, I know the total tallies are one more than the rows in the first table. I want the rows with a null column to count towards both tallies with a common factor. This file is going to go through thousands of rows so I want utmost efficiency! Thanks!
All you would need to do is create a new table and then combine an INSERT statement with a GROUP BY'd SELECT statement. This would COUNT() the number of times item1 and item2 were the same and store them in the new tally'd table.
Something along the lines of:
INSERT INTO new_tally_table (item1, item2, Tally)
SELECT item1, item2, COUNT(*)
FROM table
GROUP BY item1, item2
Edit:
Actually re-read the last bit of your question. Think what you want is something like this:
SELECT item1, item2, COUNT(*)
FROM (
SELECT i1.item1, i2.item2
FROM table1 as i1
INNER JOIN (
SELECT DISTINCT item1, item2
FROM table1 WHERE item2 IS NOT NULL
) as i2 ON (i1.item1 = i2.item1)
WHERE i1.item2 IS NULL
UNION ALL
SELECT item1, item2
FROM table1
WHERE item2 IS NOT NULL
) as t
GROUP BY item1, item2
There's probably a better way of writing that though.
There may be a simpler solution, but I can't think of it...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,item1 INT NULL
,item2 INT NULL
);
INSERT INTO my_table (item1,item2) VALUES
(1 ,NULL),
(1 ,2),
(1 ,3),
(1 ,3),
(2 ,3);
SELECT x.item1
, x.item2
, COUNT(DISTINCT y.id)
FROM my_table x
JOIN my_table y
ON y.item1 = x.item1
AND (y.item2 = x.item2 OR y.item2 IS NULL)
AND y.id <= x.id
JOIN
( SELECT item1
, item2
, MAX(id) max_id
FROM my_table
GROUP
BY item1
, item2
) z
ON z.item1 = x.item1
AND z.item2 = x.item2
AND z.max_id = x.id
WHERE x.item2 <> 0
GROUP
BY x.id;
+-------+-------+----------------------+
| item1 | item2 | COUNT(DISTINCT y.id) |
+-------+-------+----------------------+
| 1 | 2 | 2 |
| 1 | 3 | 3 |
| 2 | 3 | 1 |
+-------+-------+----------------------+
Related
Suppose I have a table like:
ID|Word |Reference
1 |Dog |1
1 |Fish |2
1 |Sheep|3
2 |Dog |4
2 |Fish |5
3 |Sheep|6
4 |Dog |7
I want to select all ID's that have the word Dog AND Sheep. So the result should be ID's: 1 and 2. I tried using this query:
SELECT ID FROM `Table` WHERE Word='Dog' OR Word='Fish' GROUP BY ID Having Word='Dog AND Word='Fish'
However, this AND in the Having clause makes me get 0 results. So, am I doing something wrong or is there another way to achieve wat I want based on MySQL query only (to optimize speed, since it has to search through many rows with the same setup as in the example above)
Basically the problem is the AND statement over multiple rows with the same ID.
UPDATE:
I need to get the reference for the ID's that where found. E.g. when the ID 1 and 2 are returned I need to know that ID 1 has reference 1 and 2. ID 2 has reference 3 and 4. Currently, I'm using this query:
SELECT ID FROM `Test` WHERE Word in ('Dog', 'Fish') GROUP BY ID HAVING count(DISTINCT Word) = 2;
Thanks
Here are two solutions that return the correct records, the first as individual records by ID and Reference, and the second with one record per ID and the Words and References as comma separated in columns.
Setup table and populate rows:
DROP TABLE IF EXISTS `list1`;
CREATE table `list1` (
id int(10),
Word varchar(10),
Reference int(10)
);
INSERT INTO `list1` (`ID`, `Word`, `Reference`)
VALUES
(1, 'Dog',1),
(1 ,'Fish',2),
(1 ,'Sheep',3),
(2 ,'Dog',4),
(2 ,'Sheep',5),
(3 ,'Sheep',6),
(4 ,'Dog',7);
Returns one row for each combination of ID and Word
SELECT
t.`ID`,
t.`Word`,
t.`Reference`
FROM `list1` as t
JOIN (
SELECT
t1.`ID` as `ref_id`
FROM `list1` AS t1
WHERE `Word` in ('Sheep','Dog')
GROUP BY t1.`ID`
HAVING count(DISTINCT t1.`Word`) = 2
) AS ts
ON t.`ID` = ts.`ref_id`
WHERE t.`Word` in ('Sheep','Dog')
ORDER BY t.`ID`,t.`Word`;
Results
ID | Word | Reference
1 | Dog | 1
1 | Sheep | 3
2 | Dog | 4
2 | Sheep | 5
Returns one row per ID, with a comma separated list of Words in one column, and a comma separated list of Reference in another.
SELECT
t.`ID`,
GROUP_CONCAT(t.`Word`) AS `Words`,
GROUP_CONCAT(t.`Reference`) AS `References`
FROM `list1` as t
JOIN (
SELECT
t1.`ID` as `ref_id`
FROM `list1` AS t1
WHERE `Word` in ('Sheep','Dog')
GROUP BY t1.`ID`
HAVING count(DISTINCT t1.`Word`) = 2
) AS ts
ON t.`ID` = ts.`ref_id`
WHERE t.`Word` in ('Sheep','Dog')
GROUP BY t.`ID`
ORDER BY t.`ID`,t.`Word`;
Results:
ID | Words | References
1 | Dog,Sheep | 1,3
2 | Dog,Sheep | 4,5
You need to join the table on itself. This way you can pick up where the id's are the same for instances where dog and sheep overlap.
Try this:
declare #t table (id int , Word varchar(10) )
insert into #t (ID, Word) values (1, 'Dog'),
(1 ,'Fish'),
(1 ,'Sheep'),
(2 ,'Dog'),
(2 ,'Sheep'),
(3 ,'Sheep'),
(4 ,'Dog')
select t.ID
from #t as t
join #t as t1 on t1.id = t.id
where t.word = 'Dog' and t1.word = 'Sheep'
Here's one way to do it by joining your table to itself.
SELECT t1.id FROM `Table` t1
INNER JOIN `Table` t2 ON t1.id = t2.id
WHERE t1.word='Dog' AND t2.word='Sheep';
The answer for my problem is solved using underneath query:
SELECT ID, GROUP_CONCAT(Reference) as ReferencesGrouped FROM `Test` WHERE Word in ('Dog', 'Fish') GROUP BY ID HAVING count(DISTINCT Word) = 2;
This will return me:
ID|ReferencesGrouped
1 |1,4
2 |4,5
I'm trying to use either a PHP file or SQL that will go through a large data table to count the amount of combinations of cells and count them in a new data_sum table.
I have an idea for a PHP file in the works:
$popdata = mysql_query("SELECT * FROM data");
while($add = #mysql_fetch_array($popdata)){
$qitem1 = "SELECT * FROM data_sum WHERE item1='".$add['item1']."'";
$ritem1 = mysql_query($qitem1);
if(mysql_num_rows($ritem1) > 0){
while($add2 = #mysql_fetch_array($ritem1)){
$qitem2 = "SELECT * FROM data_sum WHERE item2='".$add['item2']."'";
$ritem2 = mysql_query($qitem2);
if (mysql_num_rows($ritem2) > 0){
$sql = "UPDATE Count=Count + 1 WHERE item1='".$add['item1']."' AND item2='".$add['item2']."'";
$update = mysql_query($sql);
} else{
$sql = "INSERT INTO data_sum (item1, item2) VALUES('$item1', '$item2')";
$insert = mysql_query($sql);
}
} else{
$sql = "INSERT INTO data_sum (item1, item2) VALUES('$item1', '$item2')";
$insert = mysql_query($sql);
}
If I have a table :
`data`
item1 item2
1 (this row counts to both the '1,2' duo as well as the '1,3')
1 2
1 3
1 3
2 3
And I want to condense it to something like:
`data_sum`
item1 item2 Count
1 2 2
1 3 3
2 3 1
Yes, I know there are only 5 rows in data and the count is a total of 6. I want the cells with nothing in them to count towards each possible branch. Is there a way to do this in SQL with COUNT or UNIQUE or DISTINCT? Thanks!
,
The following query does what you want, by doing the calculation for the two groups separately:
select base.item1, base.item2, base.cnt + coalesce(sum(extra.cnt), 0)
from (select item1, item2, count(*) as cnt
from data
where item1 is not null and item2 is not null
group by item1, item2
) base join
(select item1, item2, count(*) as cnt
from data
where item1 is null or item2 is null
group by item1, item2
) extra
on base.item1 = extra.item1 or
base.item2 = extra.item2 or
(extra.item1 is null and extra.item2 is null)
group by base.item1, base.item2, base.cnt;
Here is a SQL Fiddle.
This is how my table looks like:
id | name | value
-----------------
1 | user1| 1
2 | user2| 1
3 | user3| 3
4 | user4| 8
5 | user5| 6
6 | user7| 4
7 | user8| 9
8 | user9| 2
What I want to do is to select all the other users, in one query, who's value is user1's value lower than it's value plus 3, higher than it's value minus 3 or equal to it's value.
Something like this:
$result = mysqli_query($con,"SELECT * FROM users WHERE value<'4' OR value>'-2'") or die("Error: ".mysqli_error($con));
while($row = mysqli_fetch_array($result))
{
echo $row['name'].'<br/>';
}
The problem is that users1's value can vary every time the query is run.
Sorry for lame names, but this should work:
NOTE: I named table with your data as "st".
SELECT b.user, a.value as "user1val", b.value as "otheruservalue" FROM st as a
join st as b
on a.user = "user1" and a.user != b.user
where
(b.value > (a.value - 3)) and (b.value < (a.value + 3))
We get unique pairs of user1's value and other user's value by joining same table. After that we just do some simple comparison to filter rows with suitable values.
$user1 = mysql_fetch_assoc(mysql_query("SELECT `value` FROM `users` WHERE id='1'"));
$result = mysql_query("SELECT * FROM `users` WHERE value<'".$user1['value']."+3' OR value>'".$user1['value']."-3'");
Or nested queries :
$result = mysqli_query($con, "select * from `users` where `value` < (select `value` from `users` where `name`='user1')+3 OR `value` > (select `value` from `users` where `name`='user1')-3");
i have database with this condition :
table hotel -----> table hotel price
table hotel :
hotel_id | hotel_name |
1 hotel1
2 hotel2
table hotel price
price_id | hotel_id | room_type | single | Double | extra |
1 1 superior 5 10 20
2 1 deluxe 3 5 10
and i would show start smallest price from hotel1
hotel1 star from "smallest value"
i tried with this but not work
$query = ("SELECT LEAST(COL1,COL2,COL3) FROM rug WHERE COL1 != '' AND COL2!= '' AND COL3 != ''");
$result=mysql_query($query);
if (!$result) {
die('Invalid query: ' . mysql_error());}
$num=mysql_numrows($result);
$i=0;
while ($i < $num)
{
$pricing[$i]=mysql_result($result, $i);
$i++;
}
sort($pricing);
$lowest_price = $pricing[0]; //lowest price
thank raymond for the answer this is almost correct
select
*
, least(single, `double`, extra) as lowest_price
from hotel_price
where
hotel_id = 1
order by
lowest_price
;
with this will show lowest_price column at hotel price table
PRICE_ID HOTEL_ID ROOM_TYPE SINGLE DOUBLE EXTRA HOTEL_NAME LOWEST_PRICE
2 1 deluxe 3 5 10 hotel1 3
1 1 superior 5 10 20 hotel1 5
but i want just show one lowest price from lowest_price column
the smallest is 3
Any thoughts? Thanks!
Not completely sure if you need this..
if you know the id of hotel with name "hotel1" already
select
*
, least(single, `double`, extra) as lowest_price
from hotel_price
where
hotel_id = 1
order by
lowest_price
;
If you don't know the id of the hotel you need to join
select
*
, least(single, `double`, extra) as lowest_price
from
hotel_price
inner join
hotel
on
hotel_price.hotel_id = hotel.hotel_id
where
hotel.hotel_name = 'hotel1'
order by
lowest_price
;
see http://sqlfiddle.com/#!2/f947b/3 for demo note the demo has more queries what should give you the same results
By your SQL syntax I presume you are using MySQL. Than you can solve this by this approach:
SELECT
(SELECT COL1 from rug) as myField
UNION
(SELECT COL2 from rug)
UNION
(SELECT COL3 from rug)
order by myField ASC LIMIT 1
My issue is that I need to paginate data from this query:
function search($search_term, $limit, $offset)
{
$id = $this->auth->get_user_id();
$query = $this->db->query("
SELECT user_id,
first_name,
cars_name,
cars_id
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
ORDER BY first_name ASC
");
$search_data = array();
foreach ($query->result() as $row) {
$search_data[$row->user_id]['name'] = $row->first_name;
$search_data[$row->user_id]['cars'][$row->cars_id] = array(
'cars_name' => $row->cars_name);
}
return $search_data;
}
A sample data table / query response would be:
1 JOE HONDA 123
1 JOE TOYOTA 124
2 MAC VW 125
2 MAC HONDA 126
2 MAC TESLA 127
3 STU SUBARU 128
3 STU KIA 129
-----------
Page 1
-----------
1 JOE HONDA 123
TOYOTA 124
2 MAC VW 125
HONDA 126
------------
Page 2
------------
3 STU SUBARU 128
KIA 129
If I enter a limit and offset at the end of MySQL query
...
LIMIT $limit
OFFSET $offset;
");
the limit and offset are applied to the total number of rows, not the the number of rows grouped by user.
I've tried using GROUP BY but was unable to make it work.
My goal is to make the query as above but LIMIT and OFFSET the query by a number of rows that counts users, not all rows.
Any ideas?
I don't see a way to do this in one query. My solution would be to get the count of unique ID's using a group by query with the same parameters:
SELECT COUNT(1) AS uid_count
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
GROUP BY user_profiles.user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
Then fetch the uid_countmysql_num_rows and use that to calculate pagination variables for the above query.
The solution really is to use a GROUP BY clause:
SELECT SQL_CALC_FOUND_ROWS
user_id,
first_name,
cars_name,
cars_id
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
GROUP BY user_id
ORDER BY first_name ASC
LIMIT 100
The order is important. GROUP BY first, then ORDER BY, and then OFFSET/LIMIT.
Notice the SQL_CALC_FOUND_ROWS up there? After the query has executed, if you want to get the total row count (including those who aren't returned because of the LIMIT clause), just use:
SELECT FOUND_ROWS() AS `count`
And fetch the count column.
However, like you said, the rows will collapse and you will lose some cars_name and cars_id values.
Another solution is to use GROUP_CONCAT, then split it in PHP:
SELECT
user_id,
first_name,
GROUP_CONCAT(cars_name SEPARATOR ','),
GROUP_CONCAT(cars_id SEPARATOR ','),
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
ORDER BY first_name ASC
LIMIT 100
This would give you something like:
1 JOE HONDA,TOYOTA 123,124
2 MAC VW,HONDA,TESLA 125,126,127
3 STU SUBARU,KIA 128,129
If you want to get a list like this
Page 1
----------------------
1 JOE HONDA 123
1 JOE TOYOTA 124
Page 2
----------------------
2 MAC VW 125
2 MAC HONDA 126
2 MAC TESLA 127
Page 3
----------------------
3 STU SUBARU 128
3 STU KIA 129
Forget about limit, do this instead:
A - First retrieve a list of user id's and insert that into a temp table
CREATE TEMPORARY TABLE `test`.`temp_user_ids` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INTEGER UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
)
ENGINE = MEMORY
B - Next insert the relavant user_id's into the table.
INSERT INTO temp_user_ids
SELECT null, user_id
FROM user_profiles
LEFT JOIN cars
ON cars.id_fk = user_id
WHERE user_id NOT LIKE '$id'
AND activated = 1
AND banned = 0
AND first_name LIKE '%$search_term%'
ORDER BY user_id DESC /*insert in reverse order !*/
The lowest user_id is the last_insert_id in the temptable, and the temp_table
items are in sequential order.
C - Set the SQL #var #current_id to the last_insert_id in the temp_table.
SELECT #current_id:= LAST_INSERT_ID()
D - Next select relevant rows from the table, using only the user_id you want.
SELECT count(*) as row_count,
up.user_id,
first_name,
group_concat(cars_name) as car_names,
group_concat(cars_id) as car_ids,
FROM user_profiles up
LEFT JOIN cars
ON cars.id_fk = up.user_id
INNER JOIN temp_user_ids t
ON (t.user_id = up.user_id)
WHERE t.id = #current_id
GROUP BY up.user_id
ORDER BY cars.id
E - Now lower the #current_id
SELECT #current_id:= #current_id - 1;
F - And repeat step D and E until there's no more rows to be had.
The first field row_count tells you the number of rows aggregated in the fields
car_names and car_ids. You can separate these fields by using php's explode.