Sorting MYSQL results by multiple columns - php

OK, so I have gone through 14 Stack Overflow suggestions that were suggested when I wrote this question, and I have tried everything, and can not figure this out.
I have a gym directory with reviews. I have a script that you can search through those gyms by Zip code, City/State or Neighborhood/City/State.
I have a few fields in the back end and in the database. The ones pertaining to this question are priority (I give it a number 1+ and it should show highest first or lowest first I don't care which preferably highest to lowest), photo (it can have 5 photos), membership includes (a few things that the gym can include in the membership), and reviews (not editable but keeps count of how many review the gym has)
I would like to sort in this order
If the gym has priority, it should show first as it is prioritized. Null Priority should come last, Then sort by photo doesn't matter a-z z-a just null comes last, then sort by membership includes null comes last, then sort by review count 0 or null comes last.
So if I have 4 gyms, A and B with priority, photo, and membership but 0 reviews, C with no priority, no photo, no membership, but highest review count at 2, and D with no priority but has photo and membership, but 1 review: it should sort in this order:
GYM Priority Photo Membership Reviews
A yes has some has count 0
B yes has some has count 0
C no no no memb. 2
D no has some has count 1
Expected sort order results: A B D C
Sorry of thats confusing.
Heres what I have already tried:
SELECT * FROM (SELECT * FROM gym WHERE (city = "Queens") AND (state = "NY") GROUP BY priority ORDER BY photo, member_includes, reviews DESC) x LIMIT 0, 150
SELECT * FROM (SELECT * FROM gym WHERE (city = "Queens") AND (state = "NY") ORDER BY priority, photo, member_includes, reviews DESC) x LIMIT 0, 150
SELECT * FROM (SELECT * FROM gym WHERE (city = "Queens") AND (state = "NY") GROUP BY photo, member_includes, reviews ORDER BY priority DESC) x LIMIT 0, 150
SELECT * FROM gym WHERE (city = "Queens") AND (state = "NY") GROUP BY photo, member_includes, reviews ORDER BY priority DESC LIMIT 0, 150
SELECT * FROM gym WHERE (city = "Queens") AND (state = "NY") ORDER BY priority, photo, member_includes, reviews DESC LIMIT 0, 150
And I have tried all other kinds of combinations with and without ASC but still it does not sort properly. I dont know what I am doing wrong.
Please help!
Thanks,
David

I believe this is what you are looking for:
SELECT * FROM gym
WHERE city = "Queens" AND state = "NY"
ORDER BY
ISNULL(priority), priority,
ISNULL(photo),
ISNULL(member_includes), member_includes,
ISNULL(reviews), reviews DESC
LIMIT 150

Here you go - I think you're missing two key ideas: use ifnull to map any null values to 0 and then sort with DESC so that the zeros (from the NULLs) sort to the end.
How does this work for you?:
create table gym (
id int primary key auto_increment not null,
name varchar(255),
priority int,
photo int,
member_includes int,
reviews int
);
insert into gym
(id, name, priority, photo, member_includes, reviews) values
(DEFAULT, 'A', 1, 2, 3, 0),
(DEFAULT, 'B', 1, 2, 3, 0),
(DEFAULT, 'C', NULL, 0, 3, 0),
(DEFAULT, 'D', NULL, 1, 3, 1);
select name from gym
order by ifnull(priority,0) desc
, ifnull(photo, 0) desc
, ifnull(member_includes, 0) desc
, ifnull(reviews, 0) desc ;
+------+
| name |
+------+
| A |
| B |
| D |
| C |
+------+
4 rows in set (0.00 sec)

Related

Sum 3 Tables and Subtract the User Request

Hello Developers/Programmers
I am working on withdrawal function on my website.
So it goes like this
I need to total the 3 tables i have by User ID with status of '1' ,and Subtract the Inputed amount by the User Requested the Withdrawal
These are my 3 tables
tbl_bonus_1
id | amount | user_id | status
1 20 1 1
2 20 1 1
3 20 3 1
tbl_bonus_2
id | amount | user_id | status
1 30 1 1
2 30 1 1
3 30 3 1
tbl_bonus_3
id | amount | user_id | status
1 40 1 1
2 40 1 1
3 40 3 1
Now I need to get all that 3 tables by USER ID and get the total of it.
After getting the total i need to subtract the Inputted quantity of the USER
and update the status to 0 so that the user cant withdraw again.
Im using Codeigniter 3.1.5
select user_id, sum(amount)
from
(select * from tbl_bonus_1
union
select * from tbl_bonus_2
union
select * from tbl_bonus_3) tt
where status = 1
group by user_id
DEMO:
http://sqlfiddle.com/#!9/7f1807e/1
And UPDATE (single user):
UPDATE tbl_bonus_1 t1 INNER JOIN tbl_bonus_2 t2
ON t1.user_id = t2.user_id
INNER JOIN tbl_bonus_3 t3
ON t1.user_id = t3.user_id
SET t1.amount = 0, t2.amount = 0, t3.amount = 0
WHERE t1.user_id = 1;
Realistically, you don't want to subtract from these tables if you want to manage a balance. You need to add a 4th table that is withdrawal amount, so you can capture the transactions. If you have a total of 160 across 3 tables, and the user withdrawals 150, how would you determine which to decrement.
I would suggest actually consolidating all of these into 1 trasaction table, and capture the amounts there.
So to get what you need you will need to leverage variables which will need to be passed to the query. Below will give you what you are asking for. That being said, this is not the correct way to do this. Also, there is no way to determine which of the 3 balances you want to subtract the withdraw from. This is just not how transaction ledgers work and for a lot of reasons I am not going to get in to right now. At the bottom of this answer is how I suggest you should build your table. You will be able to get information with more easy while capturing more/better data.
How to get data from current structure:
set #withdraw = 150.00, #user = 1;
select user_id, sum(amount) as prevBalance
, #remainingBalance := if(#user = user_id,sum(amount)-#withdraw,sum(amount)) as remainingBalance
from
(select * from tbl_bonus_1
union
select * from tbl_bonus_2
union
select * from tbl_bonus_3) balance
group by user_id;
How you should build your schema:
CREATE TABLE ledger (id int NOT NULL AUTO_INCREMENT
,user_id int
, amount decimal(5,2)
, transaction_type varchar(20)
,PRIMARY KEY (ID));
INSERT INTO ledger VALUES
(null,1,20,'Bonus1'),
(null,1,20,'Bonus1'),
(null,3,20,'Bonus1'),
(null,1,30,'Bonus2'),
(null,1,30,'Bonus2'),
(null,3,30,'Bonus2'),
(null,1,40,'Bonus3'),
(null,1,40,'Bonus3'),
(null,3,40,'Bonus3'),
(null,1,-150,'Withdraw')
;
Then all you would need to do is run the following query.
select user_id, sum(amount) balance from ledger
group by user_id;

Find out where a row ranks in a list of rows with different values

I'm trying to create a ranking system for musicians, in my mysql table I have them listed like this:
id genre artist rating votes
1 rock pink floyd 138 25
2 rock black sabbath 149 28
3 pop katy perry 98 12
4 rock the who 72 14
Users may rate an artist on a scale of 1-10, on a vote I just increase the number (1-10) as rating to the table and increase the vote by 1, so to determine the rank I would divide the rating by votes and that would be the ranking.
I understand how to create an overview which displays all of the artists with the rank,I am however trying to find an efficient way to determine the ranking of a specific artist.
Long story short: If I was to be on the black sabbath sub page, how would I determine its ranking in the rock genre ?
I hope the explanation is understandable.
SELECT COUNT(mi.genre) + 1 rank
FROM musician m
LEFT JOIN (
SELECT genre, ROUND(COALESCE(rating/votes,0),10) rating_per_vote
FROM musician
) mi
ON mi.genre = m.genre
AND mi.rating_per_vote > ROUND(COALESCE(m.rating/m.votes,0),10)
WHERE m.artist = 'black sabbath';
DEMO
UPDATE
You can actually drop the ids from the inner table, I did some playing around.
You do however need the ROUND() to make sure that the comparisons are correct.
Another solution, with less subqueries:
SELECT
m.artist,
r.rank,
r.average
FROM
musician m
JOIN
(
SELECT
id,
#rank := #rank + 1 rank,
IF(votes = 0, 0, ROUND(rating/votes, 10)) average
FROM
musician, (SELECT #rank := 0) uv
WHERE genre = 'rock'
ORDER BY average DESC
) r
ON
r.id = m.id
WHERE m.artist = 'black sabbath'

Getting a daily summary of figures from shop database

I have the following tables, in a standard shop:
(id is always primary key, auto-increment, ts is always type TIMESTAMP, updated ON_UPDATE CURRENT_TIMESTAMP)
table sales:
id | total | tendered | flag | userID | ts
1 0.6 0.6 0 4 2013-11-21 08:12:23
Sales is the parent table, userID is related to the user that made the sale. total and tendered are both of type FLOAT. flag is of type VARCHAR and could be Free Order.
table receipts:
id | oID | pID | quantity | ts
1 1 26 1 2013-11-21 08:11:25
Receipts holds a line for each unique type of product sold. oID is type INT and relates to the id of table sales. pID is of type INT and relates to the id of table products.
table products:
id | name | price | cID | display | ts
1 Mars 0.6 3 1 2014-01-17 07:55:25
Products is the central data for each product in the database. Here is a line for mars bars. cID relates to the id in table categories.
table categories
id | name | display | ts
3 Snacks 1 2013-11-14 12:06:44
Categories is the table holding all the data about each category, and can have multiple products relating to a single row. display is of type INT and dictates when the category is enabled or disabled (1 = 'true')
My question is, I want to output information like this:
**Snacks**
(name) (quantity) (price) (total)
Fruit 3 50p £1.50
Twix 1 60p 60p
Boost 1 60 60p
**Hot Drinks**
(name) (quantity) (price) (total)
English Tea 15 60p £9.00
Speciality Teas 2 60p £1.20
Which I have the following SQL for:
SELECT categories.name AS category, products.name, pID,
(SELECT SUM(quantity) FROM receipts WHERE pID=r.pID AND DATE(ts) = CURDATE()) AS quantity,
products.price,r.ts
FROM receipts r
LEFT JOIN products ON r.pID = products.id
LEFT JOIN categories ON products.cID = categories.id
WHERE DATE(r.ts) = CURDATE()
GROUP BY r.pID
ORDER BY categories.name;
Which seems to give me the correct information, but I am not 100% certain. If anyone could verify that this works, I would be most grateful. But when I want to see a particular day, I get unusual figures with the following SQL:
$postfrom = $_POST['from_mm']."/".$_POST['from_dd']."/20".$_POST['from_yy'];
$postto = $_POST['to_mm']."/".$_POST['to_dd']."/20".$_POST['to_yy'];
$from = strtotime($postfrom . " 6:00");
$to = strtotime($postto . " 23:59");
$itemised = select("SELECT categories.name AS category, products.name, pID,
(SELECT SUM(quantity) FROM receipts WHERE pID = r.pID AND UNIX_TIMESTAMP(r.ts) > '{$from}' AND UNIX_TIMESTAMP(r.ts) < '{$to}')
AS quantity, products.price
FROM receipts r
LEFT JOIN products ON r.pID = products.id
LEFT JOIN categories ON products.cID = categories.id
WHERE UNIX_TIMESTAMP(r.ts) > '{$from}'
AND UNIX_TIMESTAMP(r.ts) < '{$to}'
GROUP BY r.pID
ORDER BY categories.name;");
(function 'select' simply returns an array of the SQL table). The thing is, I could find the results easily by looping through in PHP and adding it up that way. But I know this is possible with SQL, I just don't know why It isnt working. Can somebody please help?
Edit SQL sample fiddle is here: http://sqlfiddle.com/#!2/23af4 although I couldn't do more than half a day of data due to 8000 character restrictions.
Try this:
SELECT categories.name AS category, products.name AS name,
receipts.quantity AS quantity, products.price AS price,
(receipts.quantity * products.price) AS total
FROM categories
JOIN products
ON categories.id = products.cID
JOIN receipts
ON receipts.pID = products.ID
WHERE DATE(receipts.ts) = CURDATE()
ORDER BY categories.name
SQLFiddle demo
With regard to the date restriction, you could use BETWEEN ... AND ... to specify the date and time. Using an absolute date and time moment or relative to the current day and time, for example WHERE DATE(receipts.ts) BETWEEN concat(curdate() -5,' 6:00:00 AM') AND curdate() -4

MySQL SELECT from array of ids where one is mandatory

I'd like to know the most efficient SQL query for achieving this problem:
Say we have a table with two columns, one storing entry ids (entry_id) and one storing category ids (cat_id):
entry_id cat_id
3 1
3 2
3 3
3 20
4 1
4 2
4 21
I'd like to count how many distinct entry_id's there are in the categories 1, 2 OR 3 but that also must be in cat_id 20.
For example, categories 1, 2 and 3 might represent music genres (Country, Pop etc.), while category 20 might be recording formats (CD, Vinyl etc.). So another way of putting it verbally could be: "How many products are there that are on Vinyl and in either the Pop or Country category?"
I could achieve this with a nested loop in code (PHP) or possibly with a nested SQL subquery, but neither feels that efficient. I feel there must be an obvious answer to this staring me in the face...
EDIT TO ADD:
I would also like to do this without modifying the database design, as it's a third party system.
FURTHER EXAMPLE TO CLARIFY:
Another real-world example of why I'd need this data:
Let's say the category ids instead represent either:
Accommodation Types (Camping = 20, Holiday Cottage = 21)
OR
Continents and their sub-regions (i.e. Europe = 1, UK = 2, England = 3)
Let's say someone has selected that they are interested in camping (cat_id = 1). Now we need to count how many camping products there are in the Europe. A product might be tagged as both Europe (parent), UK (child) AND England (grand-child), giving us an array of category ids 1, 2 or 3. So we now need to count how many distinct products there are in both those categories AND the original accommodation category of 1 (camping).
So having selected Camping, the end result might look something like:
Europe: 4 camping products
UK: 2 camping products
England : 1 camping product
Wales : 1 camping product
France: 2 camping products
etc.
Hope that helps...
I believe you want GROUP BY, COUNT() and EXISTS()
declare #t table(entry_id int, cat_id int)
insert #t select 1, 1
insert #t select 2, 1
insert #t select 1, 2
insert #t select 2, 2
insert #t select 3, 1
insert #t select 1, 20
select t1.cat_id, COUNT(*)
from #t as t1
where exists(
select * from #t
where t1.entry_id = entry_id
and cat_id = 20)
group by t1.cat_id
V2 using join instead of EXISTS()
declare #t table(entry_id int, cat_id int)
insert #t select 1, 1
insert #t select 2, 1
insert #t select 1, 2
insert #t select 2, 2
insert #t select 3, 1
insert #t select 1, 20
select t1.cat_id, COUNT(*)
from #t as t1
join #t as t2 on t1.entry_id = t2.entry_id and t2.cat_id = 20
group by t1.cat_id
select count(distinct entry_id) from myTable where cat_id=20 and entry_id in
(select distinct entry_id from myTable where cat_id in (1,2,3));
With no subqueries, using JOIN and GROUP BY:
Join the table to itself using entry_id (this gives you all possible pairs of cat_id for that entry_id). Select rows having cat_id both a member of (1,2,3) and the second cat_id = 20.
SELECT r1.entry_id
FROM records r1
JOIN records r2 USING(entry_id)
WHERE r1.cat_id IN (1,2,3)
AND r2.cat_id = 20 GROUP BY entry_id;

Order by ID if two users have same number of credits

:-)
I have this script, which find a users position taken from the number of credits.
It all works, but i have a little problem. If two users have the same credits, both of them will be on the same position.
Can I do, so if there are more users with same credits, then the system need to order by the users ID and out from that give them a position?
This is my code so far:
$sql = "SELECT COUNT(*) + 1 AS `number`
FROM `users`
WHERE `penge` >
(SELECT `penge` FROM `users`
WHERE `facebook_id` = ".$facebook_uid.")";
$query_rang = $this->db->query($sql);
So if i have this:
ID -------- Credits
1 -------- 100
2 -------- 100
3 -------- 120
Then the rank list should be like this:
Number 1 is user with ID 3
Number 2 is user with ID 1
Number 3 is user with ID 2
ORDER BY credits DESC, id ASC. This will sort by credits and break ties with the id.
UPDATE
I understand now that you want the ranking information for the user, not just to sort the users by credits and ids. This will give you the complete list of users and their rankings:
SELECT #rank:=#rank+1 AS rank, users.id, users.facebook_id FROM users, (SELECT #rank:=0) dummy ORDER BY penge DESC, id ASC
Getting the row number is the tricky bit solved by this blog post:
http://jimmod.com/blog/2008/09/displaying-row-number-rownum-in-mysql/
$sql = "SELECT COUNT(*) + 1 AS `number` FROM `users` WHERE `penge` > (SELECT `penge` FROM `users` WHERE `facebook_id` = ".$facebook_uid.") ORDER BY COUNT(*) + 1 desc, users.ID";
$query_rang = $this->db->query($sql);
Later EDIT:
I don't understand why you still have the same results....
I made a quick test. I have created a table:
Test: ID (Integer) and No (Integer)
I have inserted some values:
id no
1 1
1 1
1 1
2 1
3 1
4 1
4 1
5 1
Now, if I run:
SELECT
id, COUNT(*) + 1 AS `number`
FROM
test
GROUP BY
id
I get:
id number
1 4
2 2
3 2
4 3
5 2
But if I add ORDER BY:
SELECT
id, COUNT(*) + 1 AS `number`
FROM
test
GROUP BY
id
ORDER BY
count(*) desc, id
then I get:
id number
1 4
4 3
2 2
3 2
5 2

Categories