How to do Custom Order in Mysql - php

Below is my column
1-1
1-2
2-1
2-4
Is It possible to sort in this way - First Column should be be ascending and Second column should be descending
So the result will be
1-4
1-2
2-1
2-1
I don't care if the row mismatch. How can I achieve this (Something like view)
CREATE TABLE IF NOT EXISTS `pipo_orders` (
`ClientID` int(8) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT ,
);
--
-- Dumping data for table `pipo_orders`
--
INSERT INTO `pipo_orders` (`ClientID`, `created_at`, `updated_at`) VALUES
(17, '2014-11-26 16:21:36', '2014-11-26 10:51:36'),
(17, '2014-11-26 16:46:24', '2014-11-26 11:16:24'),
(17, '2014-12-04 16:45:28', '2014-12-04 11:15:28'),
(17, '2014-12-26 12:58:13', '2014-12-26 07:28:13'),
(17, '2014-12-30 14:29:31', '2014-12-30 08:59:31'),
(17, '2014-12-30 14:31:05', '2014-12-30 09:01:05'),
(17, '2015-01-02 12:20:54', '2015-01-02 06:50:54');

If this is what you want it's weird, and I can't think of a reason why you'd want it. In fact, it goes against the fundamental logic of a database...
Nevertheless:
SELECT t1.ClientID
, t1.created_at
, t2.updated_at
FROM pipo_orders t1
JOIN pipo_orders t2
ON 1 = 1
ORDER BY t1.created_at ASC
, t2.update_at DESC

That looks like a horrible thing to do. The values created_at and updated_at are related by being in the same row. You override this relation and join completely unrelated values.
However to technically do this, you would have to order the two columns separately (giving them row numbers) and then join them again:
select
col1.created_at,
col2.updated_at
from
(
select created_at, #rn1 := #rn1 + 1 as rownum
from pipo_orders po
cross join (select #rn1 := 0) as r
order by created_at
) as col1
from
(
select updated_at, #rn2 := #rn2 + 1 as rownum
from pipo_orders po
cross join (select #rn2 := 0) as r
order by updated_at desc
) as col2 using (rownum)
order by rownum;

Related

Query Mysql For Unique Values

I have a database table for fuel with the following details
The fID is the fuel id, vID is the vehicle id, volume is the amount of fuel in litres, price is the price of fuel per litre, date is the date of filling up the vehicle, type is the type/grade of fuel.
I wanted to make a query that would return the cost of fuel by multiplying the cost of fuel per litre with the volume of fuel filled up, plus another field for distance which should be the difference between the previous fill up meter and the latest meter. In the table, there will be a lot of cars, so I just want to return records of a specific vehicle (the vID will be repeated but the fID is unique).
I have the following query so far but it returns empty if there's only one fuel entry in the database, plus I can't figure out how to calculate the cost in Mysql.
Select
t1.*, t2.meter - t1.meter as distance
From fuel t1 left join fuel t2
on t1.date > t2.date
where t1.vID = t2.vID
order by t1.date desc
How should I go about it the right way?
Here is the schema:
CREATE TABLE IF NOT EXISTS `fuel` (
`fID` int(11) NOT NULL AUTO_INCREMENT,
`vID` int(11) DEFAULT NULL,
`volume` varchar(100) DEFAULT NULL,
`price` varchar(100) DEFAULT NULL,
`meter` varchar(100) DEFAULT NULL,
`date` datetime DEFAULT NULL,
`vendorID` int(11) DEFAULT NULL,
`notes` text,
`type` varchar(50) DEFAULT NULL,
PRIMARY KEY (`fID`)
) ENGINE = MyISAM DEFAULT CHARSET = latin1;
INSERT INTO `fuel` (`fID`, `vID`, `volume`, `price`, `meter`, `date`, `vendorID`, `notes`, `type`)
VALUES
(7, 28, '15', '800', '5000', '2018-05-27 05:53:00', NULL, 'Entry number one for this vehicle', 'Unleaded'),
(6, 27, '5', '1000', '2000', '2018-05-27 05:50:00', NULL, 'This is the second fill up for this vehicle', 'Leaded'),
(5, 27, '15', '1200', '1200', '2018-05-27 04:58:00', NULL, 'Hey there vendors!', 'Leaded'),
(9, 26, '25', '750', '4500', '2018-05-27 05:57:00', NULL, NULL, 'Leaded'),
(10, 26, '20', '750', '6000', '2018-05-27 05:58:00', NULL, NULL, 'Leaded');
This is how I want to output the data that will be returned. This picture takes an example of getting all fuel history logs for a vehicle vID 27. The first entry should return distance 0. The second one should subtract its current meter reading from the previous record that also has a vID of 27 (here its 800)... Any ideas how I can achieve this?
Following Nick's answer, I implemented the following in PHP, but it throws an error. However, when I run it in mysql sql command it returns the results as expected...
$vID = 27;
$pdo = $this -> con -> query("
select date_format(f1.date, '%y-%m-%d %H:%i:%s') as date,
f1.meter as mileage,
case when f2.meter is null then 0
else f1.meter - f2.meter end as distance,
f1.volume, f1.volume * f1.price as cost from fuel f1
left join fuel f2
on f2.date = (select max(date)
from fuel where fuel.vID = f1.vID and fuel.date < f1.date)
where f1.vID = ? order by f1.date ");
if($pdo -> execute([$vID]) && $pdo -> rowCount()) {
$res = $pdo -> fetchAll(5);
$this -> response($this -> json($res), 200); // send fuel logs
} else {
$this -> response('', 204); // If no records "No Content" status
}
Here is the error I get after executing the code through php.
This query will give you the individual rows that you want. The query works by joining fuel to itself using the latest fill-up date for this vehicle prior to the current fill-up date. If there is no prior fill-up date, the CASE expression produces a 0 result for distance.
SELECT DATE_FORMAT(f1.date, '%y-%m-%d %H:%i:%s') AS date,
f1.meter AS mileage,
CASE WHEN f2.meter IS NULL THEN 0
ELSE f1.meter - f2.meter
END AS distance,
f1.volume,
f1.volume * f1.price AS cost
FROM fuel f1
LEFT JOIN fuel f2
ON f2.date = (SELECT MAX(date)
FROM fuel
WHERE fuel.vID = f1.vID AND fuel.date < f1.date)
WHERE f1.vID = 27
ORDER BY f1.date
Output:
date mileage distance volume cost
18-05-27 04:58:00 1200 0 15 18000
18-05-27 05:50:00 2000 800 5 5000
Demo
If you don't want to sum the rows in PHP, the query can produce a summary row with a minor change to the query (adding an aggregation function and a GROUP BY WITH ROLLUP clause):
SELECT DATE_FORMAT(f1.date, '%y-%m-%d %H:%i:%s') AS date,
f1.meter AS mileage,
CASE WHEN f2.meter IS NULL THEN 0
ELSE f1.meter - f2.meter
END AS distance,
f1.volume,
SUM(f1.volume * f1.price) AS cost
FROM fuel f1
LEFT JOIN fuel f2
ON f2.date = (SELECT MAX(date)
FROM fuel
WHERE fuel.vID = f1.vID AND fuel.date < f1.date)
WHERE f1.vID = 27
GROUP BY f1.date WITH ROLLUP
Output:
date mileage distance volume cost
18-05-27 04:58:00 1200 0 15 18000
18-05-27 05:50:00 2000 800 5 5000
(null) 2000 800 5 23000
You can detect the summary row in PHP by the fact that the date column is null. Demo
Nicks solution will work, but the given join condition is a sub-query which runs for every join. You can also try this one, for the expected result.
select f3.date, f3.meter, f3.cost, f3.volume, f3.price, min(f3.distance) as distance from (
select f1.date,
f1.meter,
case when f2.`meter` is NULL then 0
else f1.meter - f2.meter end
as distance,
(f1.price * f1.volume) as cost,
f1.`volume`,
f1.`price`,
f1.fID
from fuel f1
left join fuel f2
on f1.vid = f2.vid and f1.`date` > f2.date
where f1.vid = 27
group by f1.fID, distance
order by date, distance asc) f3
group by f3.fID;
#Nick thanks for the feedback. Hope this query will work.
OK SQL can also achieve what you wanted, but it is more intuitive through store procedure. (i will also mention that controller and view i.e. PHP and jquery could also achive what you want but much more hard that way. In jquery case; mysql just needs to return that vehcle recordset.)
Here is the code
-- Proceuodo code
-- create a temporary table
-- set next_mileage = 0;
-- set pre_mileage = 0;
-- get a recordset of the desire vehicle
-- now loop through this recordset based on mileage
-- if pre_mileage = 0 then pre_mileage = mileage(i);
-- set next_mileage = mileage(i);
-- calculate distance (by subtracting next_mileage from previous_mileage) and other fields
DELIMITER ##
DROP PROCEDURE get_vehicle_info ##
CREATE PROCEDURE get_vehicle_info
(IN vid INT)
BEGIN
DECLARE v_vID BIGINT;
DECLARE v_volume BIGINT;
DECLARE v_price BIGINT;
DECLARE v_meter BIGINT;
DECLARE v_date datetime;
DECLARE done INT DEFAULT FALSE;
DECLARE cur1 CURSOR FOR SELECT vID, volume, price, meter, `date` FROM vehicle_records;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
DROP TEMPORARY TABLE IF EXISTS vehcle_info;
CREATE TEMPORARY TABLE vehcle_info (`Date` datetime, Mileage BIGINT, Distance BIGINT, Volume BIGINT, Cost BIGINT);
SET #sqlStr = CONCAT('CREATE OR REPLACE view vehicle_records as SELECT vID, volume, price, meter, `date` FROM fuel WHERE vID = ', vid, ' ORDER BY meter');
PREPARE stmt FROM #sqlStr;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #pre_mileage = 0;
SET #next_mileage = 0;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO v_vID, v_volume, v_price, v_meter, v_date;
IF done THEN
LEAVE read_loop;
END IF;
IF #pre_mileage = 0 THEN
SET #pre_mileage = v_meter;
END IF;
SET #next_mileage = v_meter;
INSERT INTO vehcle_info VALUES (v_date, v_meter, #next_mileage - #pre_mileage, v_volume, v_price * v_volume);
SET #pre_mileage = v_meter;
END LOOP;
CLOSE cur1;
SELECT * FROM vehcle_info;
DROP VIEW IF EXISTS vehicle_records;
DROP TEMPORARY TABLE vehcle_info;
END ##
DELIMITER ;
CALL get_vehicle_info(27);

MySQL : SELECT all USERS With 2 messages open and clicked after timestamp

I'm making a MySQL script executed in PHP.
So I have 3 tables.
Messages who contains at least 3 000 000 rows
(userid, messageid, timestamp, received, opened, clicked, deliveryid)
Users
(user(unique), profile, profile_actual_timestamp, last_delivery_id_sent)
events_clicked_data who contains at least 2 000 000 rows and detailed clicked event. This user click on this profile(like GAMES) on this message(deliveryId)
(userid, profile, deliveryId, eventDate)
So here is sample data for messages and users tables.
CREATE TABLE messages
(`user_id` varchar(100), `message_id` int, `timestamp` datetime, `received` varchar(5), `opened` varchar(5), `clicked` varchar(5), `delivery_id` int);
INSERT INTO messages
(`user_id`, `message_id`, `timestamp`, `received`, `opened`, `clicked`, `delivery_id`)
VALUES
("BillyStuff", 12,'2016-05-16 00:00:00', 'true', 'true', 'true', 8),
("BillyStuff", 11,'2016-05-14 00:00:00', 'true', 'true', 'true' , 7),
("BillyStuff", 8,'2016-04-03 00:00:00', 'true', 'false', 'false' , 6),
("BillyStuff", 4,'2016-04-02 00:00:00', 'true', 'false', 'false', 5),
("JohnDoe", 15 ,'2016-05-16 00:00:00', 'true', 'true', 'false' , 4),
("JohnDoe", 13 ,'2016-05-14 00:00:00', 'true', 'true', 'true', 3),
("Donnie", 15 ,'2016-05-16 00:00:00', 'true', 'true', 'true' , 4),
("Donnie", 13 ,'2016-05-14 00:00:00', 'true', 'true', 'true', 3)
CREATE TABLE users
(`user_id` varchar(100), `profile` varchar(100), `profile_actual_timestamp` datetime, `last_delivery_id_sent` int);
INSERT INTO users
(`user_id`, `profile`, `profile_actual_timestamp`, `last_delivery_id_sent`)
VALUES
("BillyStuff", "Game", "2016-01-01 00:00:00", 1),
("JohnDoe", "Book", "2016-01-01 00:00:00", 1),
("Donnie", "Book", "2016-05-16 00:00:00", 4)
I want to get users with 2 messages clicked after timestamp (profile_actual_timestamp means last time it was updated) in profile.
In this case I only get BillyStuff because Donnie is already up to date if I check if profile_actual_timestamp.
After this, I need to check by deliveryId and user in events_clicked's table if same categories was clicked.
CREATE TABLE events_clicked_data
(`user_id` varchar(100), `profile` varchar(100), `deliveryId` int, `eventDate` datetime);
INSERT INTO users
(`user_id`, `profile`, `deliveryId`, `eventDate`) VALUES
("BillyStuff", "Book", 8,"2016-01-01 00:00:00"),
("BillyStuff", "Book", 7,"2016-01-01 00:00:00"),
("JohnDoe", "Book", 3,"2016-01-01 00:00:00"),
("Donnie", "Book", 4,"2016-05-16 00:00:00"),
("Donnie", "Game", 3,"2016-05-16 00:00:00")
In this case, i need to update BillyStuff's profile and change it to "Book" instead of "Game" because he clicked on the same categorie twice in his last messages after the last time he was updated (profile_actual_timestamp)
So its been a really good puzzle for me this week and Im wondering if you guys can help me with this one.
originalid = userid (not necessary original, depend on table)
e.name = name of profil clicked like game.
select originalid,
name
from (
select #g := if(#u = originalid, if (#p = name, #g, #g + 1), 1) as grp,
#u := originalid as originalid,
#p := name as name
from (
select u.originalid,
m.message_sendtime_timestamp,
e.name
from bienvenue_nouveau_client_dev u
inner join messages_nouveaux_clients m
on m.originalid = u.originalid
inner join events_clicked_data e
on e.originalId = u.originalid
and e.deliveryId = m.deliveryId
where m.message_sendtime_timestamp >= u.profil_actuel_timestamp
and m.clicked = 'TRUE'
limit 1000000000000000
order by u.originalid,
m.message_sendtime_timestamp desc
) alias
) alias2
where grp = 1
group by originalid, name
having count(*) > 1
Whatever I change I got an error like this:
1250 - Table 'u' from one of the SELECTs cannot be used in global ORDER clause
The first query can be done as follows:
select u.*
from users u
inner join messages m
on m.user_id = u.user_id
where m.timestamp >= u.profile_actual_timestamp
and m.clicked = 'true'
group by u.user_id
having count(m.message_id) > 1
This second query will give you the users that used the same profile in their last two click events, if these events happened both later than the last update:
select user_id,
profile
from (
select #g := 0 + if(#u = user_id, if (#p = profile, #g, #g + 1), 1) as grp,
#u := user_id as user_id,
#p := profile as profile
from (
select u.user_id,
m.timestamp,
e.profile
from users u
inner join messages m
on m.user_id = u.user_id
left join events_clicked_data e
on e.user_id = u.user_id
and e.deliveryId = m.delivery_id
where m.timestamp >= u.profile_actual_timestamp
order by u.user_id,
m.timestamp desc
) alias
) alias2
where grp = 1
group by user_id, profile
having count(*) > 1
SQL fiddle
This query depends on variables, and is a bit risky, in that it must return the innermost results in the specified order, and that it must evaluate the middle select list (with variable assignments) in top-to-bottom order. This happens consistently, but in theory it is not guaranteed.
The (high) limit in the inner query is there to make sure the order by clause is applied, not to actually limit anything.
The variables #u and #p track the user_id and profile. Based on their previous values, the variable #g is calculated: it is reset to 1 whenever the user_id changed. Otherwise it is kept at the same value if also the profile didn't change, and it is incremented when the profile did change.
This way the grp values identify groups in which the profile is the same and uninterrupted in order of descending timestamp. The most recent group per used has number 1, which is the only one of interest in the outer query. The outer query then requires that this group 1 has more than one record (i.e. more than one occurrence of the same profile).

MySQL result set ordered by fixed positions

I've got following, simple table
Item (id, name, date, fixed_position)
(1, 'first entry', '2016-03-09 09:00:00', NULL)
(2, 'second entry', '2016-03-09 04:00:00', 1)
(3, 'third entry', '2016-03-09 05:00:00', NULL)
(4, 'fourth entry', '2016-03-09 19:00:00', NULL)
(5, 'fifth entry', '2016-03-09 13:00:00', 4)
(6, 'sixth entry', '2016-03-09 21:00:00', 2)
The number of items is not fixed, in fact can vary from ~100 to ~1000.
What i want to achieve is to perform a query to return set of Items ordered by date field which takes into consideration fixed_position field, which stands for something like "pinned" results to specific positions. If fixed_position for given entry is not NULL the result should be pinned to n-th position and if fixed_position is NULL the ORDER BY should take precedence.
Desired output of query for brighter explanation:
(2, 'second entry', '2016-03-09 04:00:00', 1) // pinned to 1-st position
(6, 'sixth entry', '2016-03-09 21:00:00', 2) // pinned to 2-nd position
(3, 'third entry', '2016-03-09 05:00:00', NULL) // ORDER BY `date`
(5, 'fifth entry', '2016-03-09 13:00:00', 4) // pinned to 4-th position
(1, 'first entry', '2016-03-09 09:00:00', NULL) // ORDER BY `date`
(4, 'fourth entry', '2016-03-09 19:00:00', NULL) // ORDER BY `date`
I've tried solution posted in Ordering MySql results when having fixed position for some items but even with copy-paste method this doesn't seem to work at all.
What I've tried this far is this query:
SELECT
#i := #i +1 AS iterator,
t.*,
COALESCE(t.fixed_position, #i) AS positionCalculated
FROM
Item AS t,
(
SELECT
#i := 0
) AS foo
GROUP BY
`id`
ORDER BY
positionCalculated,
`date` DESC
Which returns:
iterator | id | name | date | fixed_position | positionCalculated
1 1 first entry 2016-03-09 09:00:00 NULL 1
2 2 second entry 2016-03-09 04:00:00 1 1
6 6 sixth entry 2016-03-09 21:00:00 2 2
3 3 third entry 2016-03-09 05:00:00 NULL 3
4 4 fourth entry 2016-03-09 19:00:00 NULL 4
5 5 fifth entry 2016-03-09 13:00:00 4 4
Does MySQL can perform such task or should I take backend approach and perform PHP's array_merge() on two result sets?
A brute force method to solve this would be to first create a tally table having an amount of rows bigger than the original table:
SELECT #rn := #rn + 1 AS rn
FROM (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
CROSS JOIN (SELECT #rn := 0) AS v
Then you can left join this table to a derived table containing all fixed positions of your original table:
SELECT Tally.rn
FROM (
... tally table query here
) AS Tally
LEFT JOIN (
SELECT fixed_position
FROM Item
) AS t ON Tally.rn = t.fixed_position
WHERE t.t.fixed_position IS NULL
The above returns the to-be-filled missing order positions.
Demo here
You can now use the above query as yet another derived table joined to the original table to achieve the desired ordering:
SELECT id, name, `date`, fixed_position, Gaps.rn,
derived.seq, Gaps.seq
FROM (
SELECT id, name, `date`, fixed_position,
#seq1 := IF(fixed_position IS NULL, #seq1 + 1, #seq1) AS seq
FROM Item
CROSS JOIN (SELECT #seq1 := 0) AS v
ORDER BY `date`
) AS derived
LEFT JOIN (
SELECT Tally.rn,
#seq2 := #seq2 + 1 AS seq
FROM (
SELECT #rn := #rn + 1 AS rn
FROM (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
CROSS JOIN (SELECT #rn := 0) AS v
) AS Tally
LEFT JOIN (
SELECT fixed_position
FROM Item
) AS t ON Tally.rn = t.fixed_position
CROSS JOIN (SELECT #seq2 := 0) AS v
WHERE t.t.fixed_position IS NULL
ORDER BY rn
) AS Gaps ON (derived.seq = Gaps.seq) AND (derived.fixed_position IS NULL)
ORDER BY COALESCE(derived.fixed_position, Gaps.rn)
Demo here
I've had the same problem (sort by date + inject rows with fixed position values). The above solution seems to work. But you have to know how much values your table has. The line:
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
has to get extended if you have more rows because the temporary table is to short. After some google search results and tests in my DB I've figured out a solution that fits your needs and is smarter to understand I think.
SELECT *
FROM
(SELECT
create_date,
fixedposition,
#rownum:=#rownum + 1 AS colposition,
1 AS majorEntry
FROM myTable
JOIN (SELECT #rownum:=0) r
WHERE fixedposition IS NULL
ORDER BY crdate DESC) AS orderedFixed
UNION ALL
(SELECT create_date,
fixedposition,
fixedposition AS colposition,
0 AS majorEntry
FROM myTable
WHERE fixedposition IS NOT NULL
ORDER BY fixedposition ASC)
ORDER BY colposition ASC, majorEntry
So this is how it works: There are two SELECT statements.
The first SELECT searches for all columns without a fixed position and sorts them by date. Additionally it makes a JOIN to add a row counter column.
The second one searches for all rows with a fixed position and returns the sorting by "colposition".
Both select statements gets combined by an UNION ALL. The union gets sorted first by the ASCending colposition and in the second step by the majonEntry-value which indicates that the rows with fixedposition are to be placed before the other rows which have the same position.
If anyone stumbles upon this, I came up with a good solution with the help of dk1's answer. While his answer is good in principle, it has a flaw that every iteration of a pinned row has its position increased by one. For example: post pinned to the 1st position is on the 1st place (+0), post pinned to the 2nd position is on the 3rd place (+1), and post pinned to the 4th position is on the 6th place (+2).
I solved this by adding a second variable to decrease the final position of a pinned post.
Code explanation:
minor = derived table with unpinned posts
major= derived table with pinned posts
#rownum = iterator for posts in minor table
#decrease = iterator for posts in major table
SELECT
final.*
FROM
(
(
# see note #1 about this wrapper query
SELECT * FROM
(
SELECT
minor.*,
(#rownum := #rownum + 1) AS 'rowPosition',
0 AS 'rowDecrease',
0 AS 'majorEntry'
FROM table_with_your_posts minor
JOIN (#rownum := 0) x
WHERE minor.your_fixed_position IS NULL
ORDER BY minor.your_date DESC
) y
ORDER BY y.your_date DESC
)
UNION ALL
(
SELECT
major.*,
major.your_fixed_position AS 'rowPosition',
(#decrease := #decrease + 1) AS 'rowDecrease',
1 AS 'majorEntry'
FROM table_with_your_posts major
JOIN (#decrease := -1) x
WHERE major.your_fixed_position IS NOT NULL
ORDER BY major.your_fixed_position ASC
)
) final
# see note #2 about this ORDER BY clause
ORDER BY final.rowPosition - final.rowDecrease ASC, final.majorEntry DESC
note #1: Now I'm not an expert, but if you don't wrap the query of the minor table in another query and don't order it by the same thing as in the inner query, the sorting in the minor table just doesn't work. Maybe someone can shed more light on that.
note #2: ORDER BY final.rowPosition - final.rowDecrease - final.majorEntry / 2 ASC also works and looks cooler
Note that if you want to further filter your results (good example here would be to only show published posts), you will most likely need to add the filter condition to the WHERE clause of both the derived tables. Otherwise the final positions will be incorrect.
In the end I want to add that this isn't foolproof. For example if there are two pinned posts with the same position, it will shift the results by one. This could be solved by making the column with the fixed position UNIQUE. Other thing that comes to my mind is that if the table has less results than the position value of a pinned post, the post will obviously not be on its desired position (e.g. if the table contains 5 posts and you pin a post to the 8th position, it will probably end up being 5th). And remember that I didn't meaningfully test this, make sure your query is efficient for your needs.

MySQL: Use CASE/ELSE value as join parameter

I'm trying to join the NAME and PHOTO from USERS table to the TRANSACTIONS table based on who is the payer or payee. It keeps telling me can't find the table this -- What am I doing wrong?
SELECT `name`,`photo`,`amount`,`comment`,
(
CASE `payer_id`
WHEN 72823 THEN `payee_id`
ELSE `payer_id`
END
) AS `this`
FROM `transactions`
RIGHT JOIN `users` ON (`users`.`id`=`this`)
WHERE `payee_id`=72823 OR `payer_id`=72823
From the documentation about aliases:
The alias is used as the expression's column name and can be used in GROUP BY, ORDER BY, or HAVING clauses.
You can't use an alias in a join. You can use it only in the places listed above. The reason is that the alias is on a field in the result of the join. If the join were allowed to these aliases in its definition it would (or could) result in recursive definitions.
To solve your problem you could repeat the CASE clause in both places:
SELECT `name`,`photo`,`amount`,`comment`,
(
CASE `payer_id`
WHEN 72823 THEN `payee_id`
ELSE `payer_id`
END
) AS `this`
FROM `transactions`
RIGHT JOIN `users` ON `users`.`id`= (
CASE `payer_id`
WHEN 72823 THEN `payee_id`
ELSE `payer_id`
END
)
WHERE `payee_id`=72823 OR `payer_id`=72823
However I would probably rewrite this query as two selects and UNION them:
SELECT name, photo, amount, comment, payer_id AS this
FROM transactions
JOIN users ON users.id = payer_id
WHERE payee_id = 72823
UNION ALL
SELECT name, photo, amount, comment, payee_id AS this
FROM transactions
JOIN users ON users.id = payee_id
WHERE payer_id = 72823
Result:
'name3', 'photo3', 30, 'comment3', 3
'name1', 'photo1', 10, 'comment1', 1
'name2', 'photo2', 20, 'comment2', 2
Test data:
CREATE TABLE users (id INT NOT NULL, name NVARCHAR(100) NOT NULL, photo NVARCHAR(100) NOT NULL);
INSERT INTO users (id, name, photo) VALUES
(1, 'name1', 'photo1'),
(2, 'name2', 'photo2'),
(3, 'name3', 'photo3'),
(4, 'name4', 'photo4');
CREATE TABLE transactions (amount INT NOT NULL, comment NVARCHAR(100) NOT NULL, payer_id INT NOT NULL, payee_id INT NOT NULL);
INSERT INTO transactions (amount, comment, payer_id, payee_id) VALUES
(10, 'comment1', 72823, 1),
(20, 'comment2', 72823, 2),
(30, 'comment3', 3, 72823),
(40, 'comment4', 4, 5);
SELECT
th.id, th.coin_id, th.coin_family, cm.coin_id, cm.current_price
FROM
trnx_history th
JOIN
fmi_coins.coins_markets cm
ON
cm.coin_id=(CASE th.coin_family WHEN 1 THEN 1 ELSE 2 END)

Mysql SELECT with an OR across 2 columns

I'm creating a 'similar items' link table.
i have a 2 column table. both columns contains product ids.
CREATE TABLE IF NOT EXISTS `prod_similar` (
`id_a` int(11) NOT NULL,
`id_b` int(11) NOT NULL
)
INSERT INTO `prod_similar` (`id_a`, `id_b`) VALUES
(5, 10),
(5, 15),
(10, 13),
(10, 14),
(14, 5),
(14, 13);
I want to select 3 similar products, favouring products where the id is in the first col, 'id_a'
SELECT * FROM prod_similar WHERE id_a={$id} OR id_b={$id}
ORDER BY column(?)
LIMIT 3
Don't know, maybe this?
SELECT *
FROM similar_items
WHERE col_1={$id} OR col_2={$id}
ORDER BY CASE WHEN col_1={$id} THEN 0 WHEN col_2={$id} THEN 1 END
LIMIT 3
I assume you have other columns as well
(SELECT 1 favouring, id_a id, [other columns]
FROM prod_similar
WHERE id_a = {$id})
UNION
(SELECT 2 favouring, id_b id, [other columns]
FROM prod_similar
WHERE id_b = {$id})
ORDER BY favouring, id
LIMIT 3;
In case you don't mind duplicates or there are none between id_a and id_b you can do UNION ALL instead which is considerably faster.
Unions are indication of denormalized data, denormalized data improves speed of certain queries and reduces speed of others (such as this).
An easy way to do this is this:
ORDER BY NULLIF(col_1, {$id}) LIMIT 3
The CASE WHEN works as well, but this is bit simpler.
I am not sure I get the question, could you maybe post example data for the source table and also show what the result should look like.
If I got you right i would try something like
Select (case
when col_1={$ID}:
col1
when col_2={$ID}:
col2) as id from similar_items WHERE col_1={$id} OR col_2={$id}
LIMIT 3

Categories