I have 2 tables, they can be created with the following query:
CREATE TABLE transactions(Id integer,ptime date, rtime date, sid text, itemid text, gtv integer);
/* Create few records in this table */
INSERT INTO transactions VALUES(3,'2019-09-19',null,'a','a1',58);
INSERT INTO transactions VALUES(12,'2019-12-10','2019-12-15','b','b2',475);
INSERT INTO transactions VALUES(3,'2020-09-01','2020-09-02','f','f9',33);
INSERT INTO transactions VALUES(2,'2020-04-30',null,'d','d3',250);
INSERT INTO transactions VALUES(1,'2020-10-22',null,'f','f2',91);
INSERT INTO transactions VALUES(8,'2020-04-16',null,'e','e7',24);
INSERT INTO transactions VALUES(5,'2019-09-23',null,'g','g6',61);
CREATE TABLE Items(sid text , itemid text, category text, name text);
/* Create a few records in this table */
INSERT INTO Items VALUES('a','a1','pants','denimpants');
INSERT INTO Items VALUES('a','a2','tops','blouse');
INSERT INTO Items VALUES('f','f1','table','coffee table');
INSERT INTO Items VALUES('f','f5','chair','loungechair');
INSERT INTO Items VALUES('f','f6','chair','armchair');
INSERT INTO Items VALUES('d','d2','jewelry','bracelet');
INSERT INTO Items VALUES('b','b4','earphone','airpods');
Select * from NAMES;
COMMIT;
Create a flag in the transaction items table indicating whether the refund can be processed or not. The condition for a refund to be processed is that it has to happen within 72 of Purchase time.
Expected Output: Only 1 of the three refunds would be processed in this case
Create a rank by buyer_id column in the transaction items table and filter for only the second purchase per
buyer. (Ignore refunds here)
Expected Output: Only the second purchase of buyer_id 3 should the output
How will you find the second transaction time per buyer (don’t use min/max; assume there were more
transactions per buyer in the table)
Expected Output: Only the second purchase of buyer_id along with a timestamp
I have been trying to wrap my head around this, but cant seem to understand where to start.
Here is a test script treating the questions. NB I am running MariaDB. There may be adjustments needed for mySQL
We set up the database, written to be reusable.
USE test;
DROP TABLE IF EXISTS transactions;
DROP TABLE IF EXISTS items;
CREATE TABLE IF NOT EXISTS transactions(Id integer,ptime date, rtime date, sid text, itemid text, gtv integer);
/* Create few records in this table */
INSERT INTO transactions VALUES(3,'2021-09-19',null,'a','a1',58);
INSERT INTO transactions VALUES(12,'2021-12-10','2022-03-15','b','b2',475);
INSERT INTO transactions VALUES(3,'2021-09-01','2021-09-02','f','f9',33);
INSERT INTO transactions VALUES(2,'2021-12-30',null,'d','d3',250);
INSERT INTO transactions VALUES(1,'2021-10-22',null,'f','f2',91);
INSERT INTO transactions VALUES(8,'2021-04-16',null,'e','e7',24);
INSERT INTO transactions VALUES(5,'2022-01-23',null,'g','g6',61);
CREATE TABLE IF NOT EXISTS items(sid text , itemid varchar(25) PRIMARY KEY, category text, name text);
/* Create a few records in this table */
INSERT INTO items VALUES('a','a1','pants','denimpants');
INSERT INTO items VALUES('a','a2','tops','blouse');
INSERT INTO items VALUES('f','f1','table','coffee table');
INSERT INTO items VALUES('f','f5','chair','loungechair');
INSERT INTO items VALUES('f','f6','chair','armchair');
INSERT INTO items VALUES('d','d2','jewelry','bracelet');
INSERT INTO items VALUES('b','b4','earphone','airpods');
COMMIT;
Select * from transactions;
Select * from items;
Here we add a virtual column which gives us the refund status using the sales date and the request date.
request date before sales date = error
no request date = Not requested
request date 0 to 72 days after sales date = accepted
request date more then 72 days after sales date = too late
ALTER TABLE transactions ADD COLUMN refund VARCHAR(20) AS (CASE WHEN rtime is NULL THEN 'Not requested' WHEN rtime < ptime THEN "Error" WHEN DATEDIFF(rtime,ptime) > 72 THEN "too late" ELSE "accepted" END);
Select ptime,rtime,datediff(ptime,rtime) dif,refund from transactions;
To get the 2nd purchase using rank we create a query assigning rank as follows:
SELECT ID,
ptime,
RANK() OVER(PARTITION BY ID ORDER BY ptime ASC ) Rank
FROM transactions
ORDER BY ID,
Rank;
Which we can then use as a CTE and use a where to only return the second record for each I'd.
WITH rankings AS (
SELECT ID,
ptime,
RANK() OVER(PARTITION BY ID ORDER BY ptime ASC ) Rank
FROM transactions
ORDER BY ID,
Rank )
SELECT ID,ptime
FROM rankings
WHERE Rank = 2;
This is all very well but the request was for a virtual column and not a query.
When we try to use aggregation functions in a virtual column we get errors because the definition of a virtual column should only reference fields in the same row, of columns already declared in the table definition. (These lines are commented out to avoid blocking the script.
Gives an error
#ALTER TABLE transactions ADD COLUMN rank_id INT AS ( RANK() OVER ( PARTITION BY ID ORDER BY ptime ASC ));
#ERROR 1901 (HY000) at line 38: Function or expression 'rank()' cannot be used in the GENERATED ALWAYS AS clause of `rank_id`
#ALTER TABLE transactions ADD COLUMN rank_id INT AS ( RANKX(transactions.id,sum(idl)));
#ERROR 1901 (HY000) at line 41: Function or expression 'sum()' cannot be used in the GENERATED ALWAYS AS clause of `rank_id`
#ALTER TABLE transactions ADD COLUMN rank_id INT AS ( RANKX(transactions.id,id));
#ERROR 1901 (HY000) at line 55: Function or expression '`RANKX`()' cannot be used in the GENERATED ALWAYS AS clause of `rank_id`
It seems that some aggregation functions can be used in virtual columns some SQL engines. I've tried a few but I have yet to find one which works here.
I am wondering whether this is a "trick" question to encourage you to research the principales of virtual columns?
How can I limit in MySQL maximum rows for the same "user" value?
For example, I have table with columns
id | user | data
and I would like to limit maximum rows count to 5 for each user (the same "user" value).
My idea is to use transactions (InnoDB):
start transaction
insert row with user = "XXX"
count rows, where user = "XXX"
if count < 5 commit else rollback
Is my idea good or exist also another (better) solution?
I think that it's ok, You may want to make a
select count(1) from table where user='XXX'
and check the count before performing the insert.
This could be done by using special "dual" table:
INSERT INTO table (id, user, data)
SELECT 1, 'XXX', 'somedata'
FROM dual
WHERE NOT EXISTS (
SELECT user
FROM t
WHERE user = 'XXX'
GROUP BY user
HAVING COUNT(*) >= 5
)
I have an issue where I'm filtering a table by a bunch of different values. There's about 30 different filters on this table and since I'm still a novice with MySQL I have it done in a stored procedure executing multiple DELETE queries from a temporary table to filter. This example is only going to show the filter that I'm having issues from, which is a DELETE FROM table WHERE value IN () query.
Here's a test Schisma:
CREATE TABLE accounts (
user_id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(40) NOT NULL,
PRIMARY KEY(user_id)
);
CREATE TABLE blocked (
user_id INT(11) NOT NULL,
other_id INT(11) NOT NULL,
);
INSERT INTO accounts (name) VALUES ('Chris'), ('Andy');
INSERT INTO blocked (user_id, other_id) VALUES (1, 2);
The queries create two tables: the accounts table containing two rows, and the blocked table containing one row where user_id 1 has user_id 2 blocked.
Here's the query that's causing us some problem (Please note that the queries are actually more complex than displayed, but the DELETE query is 100% the same, and the issue persists through the test example provided):
BEGIN
#user_in input is a int(11) value bassed in the CALL FUNCTION(ID).
CREATE TEMPORARY TABLE IF NOT EXISTS filtered AS (SELECT * FROM accounts);
DELETE FROM filtered WHERE user_id IN (SELECT other_id FROM blocked WHERE blocked.user_id = user_in);
SELECT * FROM filtered;
END
This query should delete the row with the user_id field of 2, as in the blocked table the only row is (1, 2).
Running the SELECT query directly providing the user_id returns the other_id of 2.
SELECT other_id FROM blocked WHERE blocked.other_id = 2;
However, the stored procedure returns both rows, instead of just one. Why?
NOTE: The above query is to show what is returned when the query SELECT other_id FROM blocked WHERE blocked.user_id = user_in, another example would be SELECT other_id FROM blocked WHERE blocked.user_id = 1 granted user_in is set to 1. Both of these queries will return a set of (2) which would make the delete query look like DELETE FROM filtered WHERE user_id IN (2). This is not working, for whatever reason.
To get a simple select of that users use next query
SELECT * FROM accounts WHERE accounts.user_id NOT IN (SELECT distinct blocked.other_id from blocked)
To do it with one single select without deleting rows from temporary table use next query:
BEGIN
CREATE TEMPORARY TABLE IF NOT EXISTS filtered AS (SELECT * FROM accounts WHERE accounts.user_id NOT IN (SELECT distinct blocked.other_id from blocked));
SELECT * from filtered;
END
No need for select all in temporary table first and then delete specific rows.
Hope it helps
EDIT:
I'v read the question and still a bit confused about you problem. But i checked this solution and it works perfectly so i don't understand what is problem with this. In your procedure you have
DELETE FROM filtered WHERE user_id IN (SELECT other_id FROM blocked WHERE blocked.user_id = user_in);
and after that you say that
SELECT other_id FROM blocked WHERE blocked.other_id = 2;
And i can say that blocked.other_id and blocked.user_id are two different columns.
No disrespect but amateur mistake to mix up columns. :)
The problem here is with this statement:
DELETE FROM filtered WHERE user_id IN (SELECT other_id FROM blocked WHERE blocked.other_id = user_id);
Try changing it to this:
DELETE FROM filtered WHERE user_id
IN (SELECT other_id FROM blocked);
Reason being that the blocked table has both a other_id and a user_id column. So where you are attempting to join out to the filtered table you are in fact comparing the other_id and user_id columns in the blocked table only. Which are not equal. So no delete happens.
SELECT LAST_INSERT_ID() as id FROM table1
Why does this query sometimes return the last inserted id of another table other than table1?
I call it in Node.js (db-mysql plugin) and I can only do queries.
LAST_INSERT_ID() can only tell you the ID of the most recently auto-generated ID for that entire database connection, not for each individual table, which is also why the query should only read SELECT LAST_INSERT_ID() - without specifying a table.
As soon as you fire off another INSERT query on that connection, it gets overwritten. If you want the generated ID when you insert to some table, you must run SELECT LAST_INSERT_ID() immediately after doing that (or use some API function which does this for you).
If you want the newest ID currently in an arbitrary table, you have to do a SELECT MAX(id) on that table, where id is the name of your ID column. However, this is not necessarily the most recently generated ID, in case that row has been deleted, nor is it necessarily one generated from your connection, in case another connection manages to perform an INSERT between your own INSERT and your selection of the ID.
(For the record, your query actually returns N rows containing the most recently generated ID on that database connection, where N is the number of rows in table1.)
SELECT id FROM tableName ORDER BY id DESC LIMIT 1
I usually select the auto-incremented ID field, order by the field descending and limit results to 1. For example, in a wordpress database I can get the last ID of the wp_options table by doing:
SELECT option_id FROM wp_options ORDER BY option_id DESC LIMIT 1;
Hope that helps.
Edit - It may make sense to lock the table to avoid updates to the table which may result in an incorrect ID returned.
LOCK TABLES wp_options READ;
SELECT option_id FROM wp_options ORDER BY option_id DESC LIMIT 1;
Try this. This is working
select (auto_increment-1) as lastId
from information_schema.tables
where table_name = 'tableName'
and table_schema = 'dbName'
Most easy way:
select max(id) from table_name;
I only use auto_increment in MySQL or identity(1,1) in SQL Server if I know I'll never care about the generated id.
select last_insert_id() is the easy way out, but dangerous.
A way to handle correlative ids is to store them in a util table, something like:
create table correlatives(
last_correlative_used int not null,
table_identifier varchar(5) not null unique
);
You can also create a stored procedure to generate and return the next id of X table
drop procedure if exists next_correlative;
DELIMITER //
create procedure next_correlative(
in in_table_identifier varchar(5)
)
BEGIN
declare next_correlative int default 1;
select last_correlative_used+1 into next_correlative from correlatives where table_identifier = in_table_identifier;
update correlatives set last_correlative_used = next_correlative where table_identifier = in_table_identifier;
select next_correlative from dual;
END //
DELIMITER ;
To use it
call next_correlative('SALES');
This allows you to reserve ids before inserting a record. Sometimes you want to display the next id in a form before completing the insertion and helps to isolate it from other calls.
Here's a test script to mess around with:
create database testids;
use testids;
create table correlatives(
last_correlative_used int not null,
table_identifier varchar(5) not null unique
);
insert into correlatives values(1, 'SALES');
drop procedure if exists next_correlative;
DELIMITER //
create procedure next_correlative(
in in_table_identifier varchar(5)
)
BEGIN
declare next_correlative int default 1;
select last_correlative_used+1 into next_correlative from correlatives where table_identifier = in_table_identifier;
update correlatives set last_correlative_used = next_correlative where table_identifier = in_table_identifier;
select next_correlative from dual;
END //
DELIMITER ;
call next_correlative('SALES');
If you want to use these workarounds:
SELECT id FROM tableName ORDER BY id DESC LIMIT 1
SELECT MAX(id) FROM tableName
It's recommended to use a where clause after inserting rows. Without this you are going to have inconsistency issues.
in my table inv_id is auto increment
for my purpose this is worked
select `inv_id` from `tbl_invoice`ORDER BY `inv_id` DESC LIMIT 1;
First we start with empty table
rows = 0
Second we insert random rows let say 3400
rows = 3400
For the third time i count how many rows are in the table, then insert the new rows and after that delete rows <= from the count.
This logic only work for the first time. If that repeat the count will always be 3400 but the id will increase so it will not delete the rows
I cant use last inserted ID since the rows are random and I dont how many it will load.
// Update
"SELECT count(*) from table" - the total count so far
"INSERT INTO tab_videos_watched (id , name) values (id , name)" - this is random can be 3400 or 5060 or 1200
"DELETE FROM table WHERE idtable <= $table_count"
If id is auto incremented, then you should use like:
select max(id) from my_table;
Read this maxId into a variable and then use when issued a delete query like:
delete from my_table where id <= ?;
Replace query parameter with the last found maxId value.
Alternatively you can define a column last_inserted as datetime type.
Before next insertions, select it into a local variable.
select max(last_inserted) as 'last_inserted' from my_table;
And after insertions are made, use the last_inserted to delete records.
delete from my_table where last_inserted <= ?;
Replace query parameter with the last found last_inserted value.