Pass SELECT MAX(`Id`) FROM Table to setval() - sql

I want to pass (SELECT MAX(Id) FROM Table to mariadb's setval() function I tried with:
SELECT setval(`MySequence`, (SELECT MAX(`Id`) FROM `Table`));
but it doesn't work, I also tried:
SET #max_value = (SELECT MAX(`Id`) FROM `Table`);
SELECT setval(`MySequence`, #max_value);
how am I supposed to do this?
EDIT I made a mistake posting the question. I was using SET on the second code and is not working
EDIT As I said on the comments I'm trying to do this just once, executing from an Entity Framework Core migration. What I ended doing is executing the SELECT MAX(Id) FROM Table and recovering that value from the migration code to interpolate it later on the $"SELECT setval('sequence', {value}, true)"

I found working workaround using prepared statement:
SET #max_value = (SELECT MAX(`Id`) FROM `Table`);
EXECUTE IMMEDIATE CONCAT('SELECT SETVAL(`MySequence`, ', #max_value, ')');

In a select, use := to assign variables:
SELECT #max_value := MAX(`Id`) FROM `Table`;
SELECT setval(`MySequence`, #max_value);
You might want to add 1 to the value.
I think you can do:
SELECT setval(`MySequence`, MAX(Id))
FROM `Table`;

In a stand-alone statement (not a query), SET is generally used to assign value to a user-defined variable. Try the following instead:
SET #max_value = (SELECT MAX(`Id`) FROM `Table`);
SELECT setval(`MySequence`, #max_value);

It seems that is not possible to do this as of MariaDB 10.3.16
I've raised a bug on https://jira.mariadb.org/browse/MDEV-20111 for devs to consider adding this feature or upgrading the documentation to make explicit that it can't be done.
I worked arround this by selecting the value using the Mysqlconnector in c# code
private long ReadMaxIdFromTable()
{
MySqlConnection connection = null;
try
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder();
var builderenv = builder.AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile($"config/appsettings.{environment}.json", false, false).AddEnvironmentVariables();
IConfigurationRoot configuration = builderenv.Build();
var connectionString = configuration.GetConnectionString("ConnectionStringName");
connection = new MySqlConnection(connectionString);
connection.Open();
var cmd = connection.CreateCommand() as MySqlCommand;
cmd.CommandText = #"SELECT MAX(`Id`) FROM `Table`";
var result = (long)cmd.ExecuteScalar();
return result;
}
catch (Exception)
{
throw;
}
finally
{
if (connection != null && connection.State != System.Data.ConnectionState.Closed)
{
connection.Close();
}
}
}
Is not as clean as I would like it but, it gets the job done.
Later I use SETVAL interpolating the value in the sql string again from c# code.
var currentSeq = ReadMaxIdFromTable();
migrationBuilder.Sql($"SELECT SETVAL(`MySequence`, {currentSeq}, true);");
Also, beware that all the Up() code it's executed BEFORE anything gets to the database, this means that SELECT MAX(Id) FROM Table; has to result in the value we're looking for before the migration starts manipulating the database.

Related

Select Count(*) Query using Dapper in .Net Core API returns incorrect value

I'm trying to do a select count query in Sql Server using Dapper. The expected response should be 0 when a profile does not exist. When I do the query in SSMS it returns correctly, but in the API using Dapper it returns 1. Any idea why this is happening?
public IActionResult GetProfileCount(string profileId)
{
int profileCount = 0;
using (IDbConnection db = new SqlConnection(connectionString))
{
try
{
profileCount = db.Query($"select count(*) from Profile where Id='{profileId}'").Count();
}
catch(Exception ex)
{
Console.WriteLine($"Error retrieving count for ProfileId: {profileId}", ex.Message);
}
}
return Ok(profileCount);
}
I see you added your own answer but could I recommend not doing it that way. When you do
profileCount = db.Query($"select * from Profile where Id='{profileId}'").Count();
What you are actually doing is selecting every field from the database, pulling it into your C# application, and then counting how many results you got back. Then you are binning all that data you got back, very inefficient!
Change it to this :
profileCount = db.QueryFirst<int>($"select count(*) from Profile where Id = #profileId", new { profileId })");
Instead you are selecting an "int" from the result set, which just so happens to be your count(*). Perfect!
More on querying in Dapper here : https://dotnetcoretutorials.com/2019/08/05/dapper-in-net-core-part-2-dapper-query-basics/
Also notice that (similar to the other answer), I am using parameterized queries. I also heavily recommend this as it protects you from SQL Injection. Your initial example is very vulnerable!
You can read a little more about SQL Injection in C#/MSSQL here https://dotnetcoretutorials.com/2017/10/11/owasp-top-10-asp-net-core-sql-injection/ But just know that Dapper protects you from it as long as you use the inbuilt helpers to add parameters to your queries.
Another option is use the method ExecuteScalar for "select count" queries:
profileCount = db.ExecuteScalar<int>("select count(*) from Profile where Id=#profileId", new { profileId });
Ref.: https://www.learndapper.com/selecting-scalar-values
Try and change your query to the following:
db.Query($"select count(*) from Profile where Id = #ProfileId", new { ProfileId = profileId }).Count()
I figured it out. The .Count() is counting the rows of the result, which is going to be 1 because the result is one row displaying the number 0. I switched my code to this and it works now.
public IActionResult GetProfileCount(string profileId)
{
int profileCount = 0;
using (IDbConnection db = new SqlConnection(connectionString))
{
try
{
profileCount = db.Query($"select * from Profile where Id='{profileId}'").Count();
}
catch(Exception ex)
{
Console.WriteLine($"Error retrieving count for ProfileId: {profileId}", ex.Message);
}
}
return Ok(profileCount);
}

Breaking up a static string sql command into the command portion and seperate parameters

I have a static string with parameters, being sent to an SQL Execute command.
The strings have the format of delete 'Name' from table where x = 1 and y = 2 or select * from table where x = 1 and y = 2.
My problem is that I need to break the string into parameters.
How do I break the strings so that I can pass the command with the parameters to a single functional with the least possible work?
I have only one function to fix and handle this problem.
From this:
protected object ExecuteScaler(string queryString)
{ OpenConnection(); }
DbCommand command = _provider.CreateCommand();
command.Connection = _connection; command.CommandText = queryString; command.CommandType = CommandType.Text; if (_useTransaction) {
command.Transaction = _transaction; }
try { returnValue = command.ExecuteScalar(); } ...
Can someone please give me an example?
When you build a sql-command like this:
// don't do this because of sql injection
sql = "SELECT * FROM MyTable WHERE Col2 = " + somevalue;
Where you "break out of" the string constant to place a value, that is the point where you want to use a parameter placeholder:
// safe from sql injection
sql = "SELECT * FROM MyTable WHERE Col2 = #somevalue";
And then you can supply the value for #somevalue using the Parameters collection.
When the query needs some fixed values, then it is probably OK to keep them in the string:
// the "type" never changes for this query:
sql = "SELECT * FROM MyTable WHERE type=1 and Col2 = #somevalue";

MS Access ##identity query issue

Can someone please let me know the issue with the below query? I am running on MS Access and its giving
Syntax error in query expression 'id = ##IDENTITY'
Code:
public DosageBO SaveDosage(DosageBO dosage)
{
try
{
using (IDbConnection connection = OpenConnection())
{
StringBuilder sql = new StringBuilder();
sql.AppendLine("INSERT INTO dosage_master ( medicine_type, dosage, remarks, updateby, updatedate )");
sql.AppendLine("VALUES (#type, #dose, #remarks, #updateby, NOW());");
var parameters = new
{
type = dosage.MedicineType,
dose = dosage.Dosage,
remarks = dosage.Remarks,
updateby = Environment.UserName
};
connection.Execute(sql.ToString(), parameters);
return connection.Query<DosageBO>("SELECT medicine_type as MedicineType, dosage, remarks FROM dosage_master WHERE id = ##IDENTITY").FirstOrDefault();
}
}
catch
{
throw;
}
}
SELECT ##Identity is a specialized query. And ##Identity is only valid in that context. If you attempt to use ##Identity elsewhere, as in a WHERE clause, the db engine will throw an error.
You will have to retrieve the value from SELECT ##Identity, save it, and then use that saved value in your other query.
Remove the ) at the end
WHERE id = ##IDENTITY)
^---here
Are you inserting a row in this batch prior to the select query?
To my knowledge ##IDENTITY is only available directly after inserting a row causing an identity value to be generated i.e an insert to an autoincremental identity column.
Edit again:
Try enclosing it in a subquery e.g id = (SELECT ##IDENTITY)

ResultSet coming as empty after executing query

I have a query
SELECT instance_guid FROM service_instances WHERE service_template_guid='E578F99360A86E4EE043C28DE50A1D84' AND service_family_name='TEST'
Directly executing this returns me
4FEFDE7671A760A8DC8FC63CFBFC8316
F2F9DF641D8E2CACC03175A7A628D51D
Now I am trying same code from JDBC.
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = executionContext.getConnection();
if (conn != null) {
ps = (PreparedStatement)conn.prepareStatement(query);
if (params == null) params = new Object[0];
for (int i=0;i<params.length;i++) {
if (params[i] instanceof Integer) {
ps.setInt(i+1, ((Integer)params[i]).intValue());
} else if (params[i] instanceof java.util.Date) {
((PreparedStatement)ps).setDATE(i+1, new oracle.sql.DATE((new java.sql.Timestamp(((Date)params[i]).getTime()))));
//ps.setObject(i+1, new oracle.sql.DATE(new Time(((Date)params[i]).getTime())));
} else {
if (params[i] == null) params[i] = "";
ps.setString(i+1, params[i].toString());
}
}
rs = ps.executeQuery();
I see params[0] =E578F99360A86E4EE043C28DE50A1D84 and params[1]=TEST
But the resultSet is empty and not getting the result.I debugged but not much help?
Can you please let me know Am i trying right?
In java its defined as below
final static private String INSTANCE_GUID_BY_TEMPLATE_GUID =
"SELECT instance_guid FROM service_instances WHERE service_template_guid=? AND service_family_name=? "
SERVICE_FAMILY_NAME NOT NULL VARCHAR2(256)
SERVICE_TEMPLATE_GUID NOT NULL RAW(16 BYTE)
First and foremost this breaks every sql mapping pattern I have ever seen.
String sql = "SELECT instance_guid FROM service_instances WHERE service_template_guid=? AND service_family_name=?";
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = executionContext.getConnection();
ps = conn.prepareStatement(sql);
ps.setString(1,guid);
ps.setString(2,family);
rs = ps.executeQuery();
while(rs.next(){...}
...
}
You should not be dynamically figuring out the data types as they come in, unless you are trying to write some code to port from database X to database Y.
UPDATE
I see you are using RAW as a datatype, from this post:
As described in the Oracle JDBC Developer's guide and reference 11g,
when using a RAW column, you can treat it as a BINARY or VARBINARY
JDBC type, which means you can use the JDBC standard methods
getBytes() and setBytes() which returns or accepts a byte[]. The other
options is to use the Oracle driver specific extensions getRAW() and
setRAW() which return or accept a oracle.sql.RAW. Using these two will
require you to unwrap and/or cast to the specific Oracle
implementation class.
Further from a code readability standpoint, your solution makes it painful for a new developer to take over. Far too often I see people making sql be "dynamic" when in reality 99% of the time you don't need this level of dynamic query building. It sounds good in most people's heads but it just causes pain and suffering in the SDLC.

JavaDB anonymous column name

I am trying to create a generalized UPDATE-statement like this, where only the table-name is fixed.
updateValueQuery = conn.prepareStatement("UPDATE TABLENAME SET (?)=(?)");
That fails with an SQLException complaining about syntax. As soon as I specify the column-name like this:
updateValueQuery = conn.prepareStatement("UPDATE TABLENAME SET COL_NAME=(?)");
SQL happily compiles. Is there any way to make the columnname anonymous as well?
I am using Apache derby.
No, PreparedStatement has holders for values only.
I resolved similar problem in following way:
private final String FIND_STRING = "select * from TABLENANE where {0} = ?";
.
.
.
private final Map<String, PreparedStatement> statements = new HashMap<String, PreparedStatement>();
private PreparedStatement prepareStatement(String field, String toFind) throws SQLException{
PreparedStatement statement = null;
if (statements.contains(field)){
statement = statements.get(field);
}else{
String findInHelpDefinition = MessageFormat.format(FIND_STRING, field));
statement = connection.prepareStatement(findInHelpDefinition);
statemnts.put(field, statement);
}
statement.setString(1, toFind);
return statement;
}