MariaDB Select Distinct ON (An Expression was Expected (Near ON) - mysql

I am trying to do a simple query using Distinct On a Column (As i still want to return all other columns in the table)
But when i attempt to use the ON (Column) clause it keeps displaying an error
"An Expression was expected (Near ON)"
I have no idea what this message means, as the query appears to be formatted correctly, is this something specific with MariaDB instead of MySQL?
MariaDB version 10.3.28 If this is of any use?
SELECT DISTINCT ON (No, Branch) *
FROM Table1
ORDER BY No, a DESC;
Table1
| ID | No | Branch | Datetime |
| --------- | ---------- | ------------- | ---------------- |
| 1 | 1 | 1 | 18/10/2021 10:00 |
| 2 | 1 | 1 | 19/10/2021 10:00 |
| 3 | 1 | 2 | 22/10/2021 10:00 |
| 4 | 1 | 2 | 20/10/2021 11:37 |
| 5 | 1 | 1 | 21/10/2021 10:00 |
| 6 | 1 | 1 | 22/10/2021 11:37 |
| 7 | 2 | 1 | 20/10/2021 10:00 |
| 8 | 2 | 1 | 22/10/2021 11:37 |
Basically i was wanting to use Distinct on "No" and "Branch" to essentially give me 1 record where No and Branch are unique and its using the MAX(Datetime)
I have attempted to use Group by clause, but this gives me a row with mixed results from other rows.
The results i am after would be:
| ID | No | Branch | Datetime |
| --------- | ---------- | ------------- | ---------------- |
| 6 | 1 | 1 | 22/10/2021 11:37 |
| 3 | 1 | 2 | 22/10/2021 10:00 |
| 8 | 2 | 1 | 22/10/2021 11:37 |
I would be wanting to to display all columns in the table, as i will also be doing a Join to fetch the Name based on No and Branch

Try:
CREATE TABLE table_tst (
`ID` int(5),
`No` int(5),
`Branch` int(5),
`Datetime` Datetime
);
INSERT INTO table_tst VALUES
(1,1,1,'2021-10-18 10:00:00'),
(2,1,1,'2021-10-19 10:00:00'),
(3,1,2,'2021-10-22 10:00:00'),
(4,1,2,'2021-10-20 11:37:00'),
(5,1,1,'2021-10-21 10:00:00'),
(6,1,1,'2021-10-22 11:37:00'),
(7,2,1,'2021-10-20 10:00:00'),
(8,2,1,'2021-10-22 11:37:00');
select *
from table_tst where (No,Branch,Datetime) in
(
select `No`,`Branch`,MAX(`Datetime`) as max_time
from table_tst
group by `No`,`Branch`
)
order by No ,Branch ASC;
Demo: https://www.db-fiddle.com/f/vhqJXYFy52xRtVBc97R1EL/8
datetime is a reserved word in mysql : https://dev.mysql.com/doc/refman/8.0/en/keywords.html and it should be in backticks or I suggest using other non reserved words

Related

Distinct order-number sequence for every customer

I have table of orders. Each customer (identified by the email field) has his own orders. I need to give a different sequence of order numbers for each customer. Here is example:
----------------------------
| email | number |
----------------------------
| test#com.com | 1 |
----------------------------
| example#com.com | 1 |
----------------------------
| test#com.com | 2 |
----------------------------
| test#com.com | 3 |
----------------------------
| client#aaa.com | 1 |
----------------------------
| example#com.com | 2 |
----------------------------
Is possible to do that in a simple way with mysql?
If you want update data in this table after an insert, first of all you need a primary key, a simple auto-increment column does the job.
After that you can try to elaborate various script to fill the number column, but as you can see from other answer, they are not so "simple way".
I suggest to assign the order number in the insert statement, obtaining the order number with this "simpler" query.
select coalesce(max(`number`), 0)+1
from orders
where email='test1#test.com'
If you want do everything in a single insert (better for performance and to avoid concurrency problems)
insert into orders (email, `number`, other_field)
select email, coalesce(max(`number`), 0) + 1 as number, 'note...' as other_field
from orders where email = 'test1#test.com';
To be more confident about not assign at the same customer two orders with the same number, I strongly suggest to add an unique constraint to the columns (email,number)
create a column order_number
SELECT #i:=1000;
UPDATE yourTable SET order_number = #i:=#i+1;
This will keep incrementing the column value in order_number column and will start right after 1000, you can change the value or even you can even use the primary key as the order number since it is unique all the time
I think one more need column for this type of out put.
Example
+------+------+
| i | j |
+------+------+
| 1 | 11 |
| 1 | 12 |
| 1 | 13 |
| 2 | 21 |
| 2 | 22 |
| 2 | 23 |
| 3 | 31 |
| 3 | 32 |
| 3 | 33 |
| 4 | 14 |
+------+------+
You can get this result:
+------+------+------------+
| i | j | row_number |
+------+------+------------+
| 1 | 11 | 1 |
| 1 | 12 | 2 |
| 1 | 13 | 3 |
| 2 | 21 | 1 |
| 2 | 22 | 2 |
| 2 | 23 | 3 |
| 3 | 31 | 1 |
| 3 | 32 | 2 |
| 3 | 33 | 3 |
| 4 | 14 | 1 |
+------+------+------------+
By running this query, which doesn't need any variable defined:
SELECT a.i, a.j, count(*) as row_number FROM test a
JOIN test b ON a.i = b.i AND a.j >= b.j
GROUP BY a.i, a.j
Hope that helps!
You can add number using SELECT statement without adding any columns in table orders.
try this:
SELECT email,
(CASE email
WHEN #email
THEN #rownumber := #rownumber + 1
ELSE #rownumber := 1 AND #email:= email END) as number
FROM orders
JOIN (SELECT #rownumber:=0, #email:='') AS t

MySQL - Recursive Reorder Algorithm / UPDATE with Variable Incrementation

Sorry for the kind of meaningless title, but I couldn't come up with a more fitting one.
I have a MySQL table, which looks like this:
SELECT * FROM `table`
+----+-----------+----------+-------+
| id | dimension | order_by | value |
+----+-----------+----------+-------+
| 1 | 1 | 1 | 1st |
| 2 | 1 | 100 | 3rd |
| 3 | 2 | 300 | 5th |
| 4 | 3 | 999 | 6th |
| 5 | 1 | 2 | 2nd |
| 6 | 2 | 1 | 4th |
+----+-----------+----------+-------+
I am listing all entries ordered by dimension (first) and order_by (second), which looks like this:
SELECT * FROM `table` ORDER BY `dimension`, `order_by`
+----+-----------+----------+-------+
| id | dimension | order_by | value |
+----+-----------+----------+-------+
| 1 | 1 | 1 | 1st |
| 5 | 1 | 2 | 2nd |
| 2 | 1 | 100 | 3rd |
| 6 | 2 | 1 | 4th |
| 3 | 2 | 300 | 5th |
| 4 | 3 | 999 | 6th |
+----+-----------+----------+-------+
Now I'd like to write a function, that rearranges the order_by, if possible with just one update query, to make it look that way:
SELECT * FROM `table` ORDER BY `dimension`, `order_by`
+----+-----------+----------+-------+
| id | dimension | order_by | value |
+----+-----------+----------+-------+
| 1 | 1 | 1 | 1st |
| 5 | 1 | 2 | 2nd |
| 2 | 1 | 3 | 3rd |
| 6 | 2 | 1 | 4th |
| 3 | 2 | 2 | 5th |
| 4 | 3 | 1 | 6th |
+----+-----------+----------+-------+
What I got so far (which, unfortunately, doesn't start recounting for each dimension):
UPDATE `table` AS `l`
JOIN (SELECT #i=1 FROM `table`) AS `i`
SET `order_by` = #i:=i
Now, my question would be: Is it possible to do it with just one UPDATE query?
You have to introduce another variable holding the value of the previous row.
UPDATE Table1 t
INNER JOIN (
SELECT
id, /*your primary key I assume*/
#new_ob:=if(#prev != dimension, 1, #new_ob + 1) as new_ob,
#prev := dimension /*In this line, the value of the current row is assigned. In the previous line, the variable still holds the value of the previous row*/
FROM
Table1
, (SELECT #prev := null, #new_ob := 0) var_init_subquery
ORDER BY dimension, order_by
) st ON t.id = st.id
SET t.order_by = st.new_ob;
see it working live in an sqlfiddle

How to create a simple crosstab query in MySQL

I have two tables containing fields as below.
Table 1
| SetID | InQty | Day |
| 1 | 10 | 1 |
| 2 | 10 | 2 |
| 3 | 10 | 3 |
Table 2
| SetID | OtQty | Day |
| 1 | 1 | 5 |
| 1 | 2 | 6 |
| 1 | 3 | 7 |
SetID in table 2 is linked with SetId in table 1. Day is placed in place of date, just for convenience only. Expected Output,
| Day | InQty | OtQty |
| 1 | 10 | |
| 5 | | 1 |
| 6 | | 2 |
| 7 | | 3 |
Blank Space can be filled with NULL or Zero.
It appears you are querying ONLY for set ID = 1 otherwise, I would expect to see in/out values for Set 2 and 3. You should be able to get with a simple UNION
select t1.Day, t1.InQty, 0 OutQty
from Table1 t1
where SetID = 1
order by t1.Day
union select t2.Day, 0, t2.OtQty
from Table2 t2
where SetID = 1
Now, if you want totals spanning different "setID"s and keeping them differentiated from each other, just add the setID as a column and also add to the group by clause as well.

Extend Mysql table for same id

My table structure is:
id | type | attribute | customer_id | value
1 | 2 | 1 | 1 | some
2 | 2 | 2 | 1 | this
3 | 2 | 3 | 1 | that
4 | 2 | 1 | 2 | cool
5 | 2 | 2 | 2 | just
etc
I want to add value='mine' as attribute 4 to each customer_id.
INSERT INTO mytable
SET type='2', attribute='4, value='mine'
The question is how to bind it on customer_id and only once per customer?
INSERT INTO myTable(type, attribute, customer_id, value)
SELECT 2 type,
4 attribute,
s.customer_id,
'mine' `value`
FROM (SELECT DISTINCT customer_id FROM myTable) s

Top 'n' results for each keyword

I have a query to get the top 'n' users who commented on a specific keyword,
SELECT `user` , COUNT( * ) AS magnitude
FROM `results`
WHERE `keyword` = "economy"
GROUP BY `user`
ORDER BY magnitude DESC
LIMIT 5
I have approx 6000 keywords, and would like to run this query to get me the top 'n' users for each and every keyword we have data for. Assistance appreciated.
Since you haven't given the schema for results, I'll assume it's this or very similar (maybe extra columns):
create table results (
id int primary key,
user int,
foreign key (user) references <some_other_table>(id),
keyword varchar(<30>)
);
Step 1: aggregate by keyword/user as in your example query, but for all keywords:
create view user_keyword as (
select
keyword,
user,
count(*) as magnitude
from results
group by keyword, user
);
Step 2: rank each user within each keyword group (note the use of the subquery to rank the rows):
create view keyword_user_ranked as (
select
keyword,
user,
magnitude,
(select count(*)
from user_keyword
where l.keyword = keyword and magnitude >= l.magnitude
) as rank
from
user_keyword l
);
Step 3: select only the rows where the rank is less than some number:
select *
from keyword_user_ranked
where rank <= 3;
Example:
Base data used:
mysql> select * from results;
+----+------+---------+
| id | user | keyword |
+----+------+---------+
| 1 | 1 | mysql |
| 2 | 1 | mysql |
| 3 | 2 | mysql |
| 4 | 1 | query |
| 5 | 2 | query |
| 6 | 2 | query |
| 7 | 2 | query |
| 8 | 1 | table |
| 9 | 2 | table |
| 10 | 1 | table |
| 11 | 3 | table |
| 12 | 3 | mysql |
| 13 | 3 | query |
| 14 | 2 | mysql |
| 15 | 1 | mysql |
| 16 | 1 | mysql |
| 17 | 3 | query |
| 18 | 4 | mysql |
| 19 | 4 | mysql |
| 20 | 5 | mysql |
+----+------+---------+
Grouped by keyword and user:
mysql> select * from user_keyword order by keyword, magnitude desc;
+---------+------+-----------+
| keyword | user | magnitude |
+---------+------+-----------+
| mysql | 1 | 4 |
| mysql | 2 | 2 |
| mysql | 4 | 2 |
| mysql | 3 | 1 |
| mysql | 5 | 1 |
| query | 2 | 3 |
| query | 3 | 2 |
| query | 1 | 1 |
| table | 1 | 2 |
| table | 2 | 1 |
| table | 3 | 1 |
+---------+------+-----------+
Users ranked within keywords:
mysql> select * from keyword_user_ranked order by keyword, rank asc;
+---------+------+-----------+------+
| keyword | user | magnitude | rank |
+---------+------+-----------+------+
| mysql | 1 | 4 | 1 |
| mysql | 2 | 2 | 3 |
| mysql | 4 | 2 | 3 |
| mysql | 3 | 1 | 5 |
| mysql | 5 | 1 | 5 |
| query | 2 | 3 | 1 |
| query | 3 | 2 | 2 |
| query | 1 | 1 | 3 |
| table | 1 | 2 | 1 |
| table | 3 | 1 | 3 |
| table | 2 | 1 | 3 |
+---------+------+-----------+------+
Only top 2 from each keyword:
mysql> select * from keyword_user_ranked where rank <= 2 order by keyword, rank asc;
+---------+------+-----------+------+
| keyword | user | magnitude | rank |
+---------+------+-----------+------+
| mysql | 1 | 4 | 1 |
| query | 2 | 3 | 1 |
| query | 3 | 2 | 2 |
| table | 1 | 2 | 1 |
+---------+------+-----------+------+
Note that when there are ties -- see users 2 and 4 for keyword "mysql" in the examples -- all parties in the tie get the "last" rank, i.e. if the 2nd and 3rd are tied, both are assigned rank 3.
Performance: adding an index to the keyword and user columns will help. I have a table being queried in a similar way with 4000 and 1300 distinct values for the two columns (in a 600000-row table). You can add the index like this:
alter table results add index keyword_user (keyword, user);
In my case, query time dropped from about 6 seconds to about 2 seconds.
You can use a pattern like this (from Within-group quotas (Top N per group)):
SELECT tmp.ID, tmp.entrydate
FROM (
SELECT
ID, entrydate,
IF( #prev <> ID, #rownum := 1, #rownum := #rownum+1 ) AS rank,
#prev := ID
FROM test t
JOIN (SELECT #rownum := NULL, #prev := 0) AS r
ORDER BY t.ID
) AS tmp
WHERE tmp.rank <= 2
ORDER BY ID, entrydate;
+------+------------+
| ID | entrydate |
+------+------------+
| 1 | 2007-05-01 |
| 1 | 2007-05-02 |
| 2 | 2007-06-03 |
| 2 | 2007-06-04 |
| 3 | 2007-07-01 |
| 3 | 2007-07-02 |
+------+------------+