Avatar billede GoofyDK Nybegynder
20. december 2010 - 22:22 Der er 57 kommentarer og
2 løsninger

MySQL, tjekke om den kan UPDATE ellers bruge INSERT.

Hej Eksperten.

Jeg har lavet et MySQL script, og det hele virker perfekt. Men problemet er bare, at jeg skal kunne finde info om et brugernavn allerede er der.

Lige nu bruger jeg try { UPDATE } catch { INSERT }. Men nogen gange så kommer de på flere gange, jeg ved ikke lige hvorfor. Nogen som kan hjælpe mig med en simpel udgave, af at tjekke om et brugernavn allerede findes i feltet "Brugernavn"?

public void MySQLDATA()
        {
            try
            {
                string query = "UPDATE users SET kode='21' WHERE brugernavn='Test'";
                if (this.OpenConnection() == true)
                {
                    MySqlCommand cmd = new MySqlCommand(query, connection);
                    cmd.ExecuteNonQuery();
                    this.CloseConnection();
                }
            }
            catch
            {
                string query = "INSERT INTO users (brugernavn, kode) VALUES('Test', '123')";
                if (this.OpenConnection() == true)
                {
                    MySqlCommand cmd = new MySqlCommand(query, connection);
                    cmd.ExecuteNonQuery();
                    this.CloseConnection();
                }
            }
        }

Ved ikke hvor meget I får ud af dette, men håber I kan hjælpe mig videre :-)
Avatar billede michael_stim Ekspert
20. december 2010 - 22:29 #1
Avatar billede Syska Mester
20. december 2010 - 22:47 #2
Du bør først finde ud af om brugeren findes.

Findes han ikke, så skal du lave en insert, findes han, jamen så skal du lave en update.

Overstående kode er ... IRKKK.

Hvis du vil have lidt sjov læsning: http://www.xaprb.com/blog/2006/02/21/flexible-insert-and-update-in-mysql/

I MSSQL ville jeg klart bruger MERGE, men den findes ikke i MySQL.

mvh
Avatar billede arne_v Ekspert
21. december 2010 - 00:06 #3
Hvis der ikke er andre felter i tabellen så kunne MySQL REPLACE måske bruges.

http://dev.mysql.com/doc/refman/5.5/en/replace.html
Avatar billede arne_v Ekspert
21. december 2010 - 00:07 #4
Ellers undrer jeg mig meget over rækkefølgen.

I de fleste databaser sletter man ikke rækker.

Og så er INSERT+UPDATE mere naturlig end UPDATE+INSERT, fordi den sidste kan give 2 fejl mens det kan den første ikke.
Avatar billede arne_v Ekspert
21. december 2010 - 00:08 #5
buzzzz's løsning med ettest (SELECT) først er meget praktisk.

Husk dog at enten skal du også håndtere fejl fordi en anden indsætter efter test eller så skal du have højt transaction isolation level.
Avatar billede Syska Mester
21. december 2010 - 09:12 #6
hmm, haha Arne, det kan du faktisk have ret i. Omend det ikke ville være noget man ville ramme specielt tit, så kan det jo selvfølgelig ske som du siger.

Men vil den ikke lave en lock på den table ?

mvh
Avatar billede arne_v Ekspert
21. december 2010 - 15:25 #7
Afhaenger af transaction isolation level.

repeatable read og serialized : ja

andre : nej
Avatar billede arne_v Ekspert
21. december 2010 - 15:29 #8
Hm. Eller er det kun ved serializable? Tror jeg faktisk ved naermere eftertanke.
Avatar billede GoofyDK Nybegynder
21. december 2010 - 18:40 #9
Tak for kommentarerne, jeg vil lige kigge lidt på det. Men der er ikke en måde, ligesom i PHP. Hvor man kan if(mysql_query(result) == 1) { Der findes allerede data }

Jeg mener nok, det er sådan. :-)
Avatar billede Syska Mester
21. december 2010 - 22:47 #10
Nu er det lang tid siden jeg har kigget på php ..

Men i følge:
http://dk.php.net/manual/en/function.mysql-query.php

Passer din syntax ikke ...

Du kan i php gøre noget ala:
http://dk.php.net/manual/en/function.mysql-num-rows.php
<?php

$link = mysql_connect("localhost", "mysql_user", "mysql_password");
mysql_select_db("database", $link);

$result = mysql_query("SELECT * FROM table1", $link);
$num_rows = mysql_num_rows($result);

echo "$num_rows Rows\n";

?>


Som vil give dig antal rows ... og så er vi tilbage til arne og min version ... du bliver nød til på en måde først at sikre dig at brugeren ikke findes ved en select
SELECT Id FROM users WHERE Email = 'foo@bar.tld'

Affected rows af din select vil så være 1 eller 0 alt efter om han findes. Jeg går ud fra du har en unique constraint på Email, så den ikke kan eksistere flere gange.

Så i php ... er det præcis det samme.
Avatar billede GoofyDK Nybegynder
23. december 2010 - 22:18 #11
Kan du give mig et C# eksempel, for jeg synes jeg er lidt låst.
Avatar billede GoofyDK Nybegynder
26. december 2010 - 18:33 #12
Det vil måske være sådan:

string query = "INSERT INTO users (brugernavn, kode) VALUES('Test', '123') ON DUPLICATE KEY UPDATE users SET kode='21' WHERE brugernavn='Test'";

if (this.OpenConnection() == true)
                {
                    MySqlCommand cmd = new MySqlCommand(query, connection);
                    cmd.ExecuteNonQuery();
                    this.CloseConnection();
                }

Er det korrekt?
Avatar billede Syska Mester
27. december 2010 - 17:23 #13
Hvis det virker er det vel fint.

Men jeg ville nok lave en count eller exist ... og se om brugeren er oprettet ... hvis han er det, ja, så giv en fejl besked ellers opret ham.

mvh
Avatar billede Syska Mester
27. december 2010 - 17:24 #14
eller brug en "MERGE" i MSSQL 2008 ...

mvh
Avatar billede arne_v Ekspert
27. december 2010 - 17:39 #15
Hvad er forskellen på SQLServer MERGE og MySQL ON DUPLICATE KEY?
Avatar billede Syska Mester
27. december 2010 - 17:48 #16
haha ... ups, havde ikke set det var MySQL ... læste bare de sidste par linjer.

Men der er vel ingen forskel ... eneste kunne måske være, at ved MERGE kan du selv definere hvad der skal sammenlignes ...

Lyder ikke helt til din kan det i MySQL ... hvis det ikke er en KEY.

mvh
Avatar billede arne_v Ekspert
27. december 2010 - 17:53 #17
I langt de fleste tilfælde må det være PK man vil teste på.

Min pointe var så at hvis MERGE er en god løsning i SQLServer så må ON DUPLICATE KEY også være en god løsning i MySQL.

Jeg tror ikek at jeg ville bruge nogen af dem, men det er så en anden sag.
Avatar billede Syska Mester
27. december 2010 - 18:11 #18
Nej, det er så en helt anden ting.

Jeg ville nok også selv tjekke om brugeren findes i forvejen. Mest for ikke at vælge nogen mssql/mysql specifikke løsninger.

Men er der andre grunde end det, siden du ikke vil vælge dem?

mvh
Avatar billede arne_v Ekspert
27. december 2010 - 19:19 #19
Det er grunden til at jeg ville undgå dem.

Database specifik SQL kan være nødvendig, men det er at skyde sig selv i foden at bruge det, hvis det ikke er nødvendigt.
Avatar billede GoofyDK Nybegynder
27. december 2010 - 20:04 #20
Arne, vil du vise et forslag på, hvordan du ville lave det?
Avatar billede arne_v Ekspert
27. december 2010 - 20:08 #21
Lige ud af landevejen kode med enten:

INSERT
UPDATE

eller:

SELECT
INSERT
UPDATE
Avatar billede GoofyDK Nybegynder
27. december 2010 - 20:15 #22
Ja, det er jo også det jeg bruger i min kode i det første indlæg. Men det virker jo bare ikke sådan 100% :)
Avatar billede arne_v Ekspert
27. december 2010 - 20:21 #23
Nej. Du bruger:

UPDATE
INSERT
Avatar billede GoofyDK Nybegynder
27. december 2010 - 20:26 #24
Ja, hvis den ikke kan update, så skal den insert.

Jeg kan bare ikke forstå det ikke vil virke. Og min ON DUPLICATE KEY UPDATE virker heller ikke.
Avatar billede arne_v Ekspert
27. december 2010 - 20:36 #25
Som jeg forklarede i # 4 duer UPDATE INSERT ikke.
Avatar billede arne_v Ekspert
27. december 2010 - 20:36 #26
Og hvad er fejlen ved ON DUPLICATE KEY UPDATE ?
Avatar billede GoofyDK Nybegynder
27. december 2010 - 20:47 #27
MySql.Data.MySqlClient.MySqlException (0x80004005): You have an error in your SQ
L syntax; check the manual that corresponds to your MySQL server version for the
right syntax to use near 'SET Kode='21' WHERE Brugernavn='1'' at line 1
  at MySql.Data.MySqlClient.MySqlStream.ReadPacket()
  at MySql.Data.MySqlClient.NativeDriver.GetResult(Int32& affectedRow, Int32& i
nsertedId)
  at MySql.Data.MySqlClient.Driver.GetResult(Int32 statementId, Int32& affected
Rows, Int32& insertedId)
  at MySql.Data.MySqlClient.Driver.NextResult(Int32 statementId)
  at MySql.Data.MySqlClient.MySqlDataReader.NextResult()
  at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior behavior
)
  at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader()
  at MySql.Data.MySqlClient.MySqlCommand.ExecuteNonQuery()

Og jeg har prøvet med 'SET Kode='21' WHERE Brugernavn='1'' - det hjalp heller ikke.

string query = "INSERT INTO test (Brugernavn, Kode) VALUES (1,2) ON DUPLICATE KEY UPDATE test SET Kode='21' WHERE Brugernavn='1'";
Avatar billede arne_v Ekspert
27. december 2010 - 20:53 #28
Så vidt jeg kan se i docs skal SET keywordet ikke bruges i denne sammenhæng.
Avatar billede arne_v Ekspert
27. december 2010 - 20:54 #29
Og tabelnavnet skal heller ikke angives.
Avatar billede arne_v Ekspert
27. december 2010 - 20:54 #30
Så prøv:

string query = "INSERT INTO test (Brugernavn, Kode) VALUES (1,2) ON DUPLICATE KEY UPDATE Kode='21' WHERE Brugernavn='1'";
Avatar billede GoofyDK Nybegynder
27. december 2010 - 21:01 #31
Efter dit forslag fik jeg denne fejl:

MySql.Data.MySqlClient.MySqlException (0x80004005): You have an error in your SQ
L syntax; check the manual that corresponds to your MySQL server version for the
right syntax to use near 'WHERE Brugernavn='1'' at line 1
  at MySql.Data.MySqlClient.MySqlStream.ReadPacket()
  at MySql.Data.MySqlClient.NativeDriver.GetResult(Int32& affectedRow, Int32& i
nsertedId)
  at MySql.Data.MySqlClient.Driver.GetResult(Int32 statementId, Int32& affected
Rows, Int32& insertedId)
  at MySql.Data.MySqlClient.Driver.NextResult(Int32 statementId)
  at MySql.Data.MySqlClient.MySqlDataReader.NextResult()
  at MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior behavior
)
  at MySql.Data.MySqlClient.MySqlCommand.ExecuteNonQuery()
Avatar billede arne_v Ekspert
27. december 2010 - 21:24 #32
WHERE er heller ikke nødvendig:

string query = "INSERT INTO test (Brugernavn, Kode) VALUES (1,2) ON DUPLICATE KEY UPDATE Kode='21'";
Avatar billede GoofyDK Nybegynder
27. december 2010 - 21:26 #33
Nu opretter den bare brugeren hver gang, selvom han lige er blevet oprettet.

Hvad er der så galt? :|
Avatar billede arne_v Ekspert
27. december 2010 - 21:37 #34
Konstruktionen antager at BrugerNavn er primary key.

Er den ikke det?
Avatar billede GoofyDK Nybegynder
27. december 2010 - 21:41 #35
Nej det er 'ID' der er primær.
Avatar billede arne_v Ekspert
27. december 2010 - 21:47 #36
Så duer metoden ikke.

SELECT
INSERT
UPDATE
Avatar billede GoofyDK Nybegynder
27. december 2010 - 21:49 #37
Hvordan sletter man så den primære nøgle?

Og vil du være sød at komme med den demonstration på, hvordan det virker?
Avatar billede arne_v Ekspert
28. december 2010 - 00:58 #38
Hvis du er ved at lave din app kan du vil bare starte forfra. Er det en database som allerede er i brug skal du nok ikke pille ved PK.

SELECT
INSERT
UPDATE

er lige ud af landevejen.

3 x MySqlCommand
Avatar billede GoofyDK Nybegynder
28. december 2010 - 07:43 #39
Det er en database, hvor der allerede er data i.

Men jeg forstår slet ikke, hvorfor jeg skal bruge INSERT og så UPDATE.

"SELECT * FROM test WHERE Brugernavn = '1'";

og så går jeg i stå, for jeg er ikke sikker på, hvordan jeg skal få svar på det.
Avatar billede arne_v Ekspert
28. december 2010 - 18:06 #40
Med SELECT kan du teste om der på tidspunkt for SELECT allerede var en række.
Avatar billede arne_v Ekspert
28. december 2010 - 18:07 #41
Forudsat at du aldrig bruger DELETE, så vil INSERT+UPDATE altid gå godt, mens UPDATE+INSERT kan fejle fordi der indsættes en række mellem de to.
Avatar billede GoofyDK Nybegynder
28. december 2010 - 19:53 #42
DELETE forkommer, når en bruger sletter sin bruger.

Men jeg er helt lost i hvad du mener arne.
Avatar billede GoofyDK Nybegynder
28. december 2010 - 20:48 #43
Jeg har fundet sådan en php kode:

$result = mysql_query("update test set col='test' where col_id='1';");       
if (mysql_affected_rows()==0) {
    $result = mysql_query("insert into test (col_id, col) values ('1','test');");
}

Hvordan skal den omskrives til C#?
Avatar billede arne_v Ekspert
28. december 2010 - 22:40 #44
Lige ud af landevejen.

2 x SqlCommand med hver sin SQL i.s.f. de 2 x mysql_query

Det lille trick er at cmd.ExecuteNonQuery() faktisk returnerer antal rows!
Avatar billede GoofyDK Nybegynder
29. december 2010 - 17:17 #45
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            try
            {
                string brugernavn = "2";

                string query = "SELECT * FROM test WHERE Brugernavn = '" + brugernavn + "'";
                string query_update = "update test set Kode='test' where Brugernavn='" + brugernavn + "'";
                string query_new = "insert into test (Brugernavn, Kode) values ('" + brugernavn + "','test_new')";
                string connectionString;
                connectionString = "SERVER=" + MySQLserver + ";" + "DATABASE=" + MySQLdatabase + ";" + "UID=" + MySQLuid + ";" + "PASSWORD=" + MySQLpassword + ";";

                connection = new MySqlConnection(connectionString);

                connection.Open();

                MySqlCommand MySQLcmd = new MySqlCommand(query, connection);
                Console.WriteLine(MySQLcmd.ExecuteNonQuery());
                if (MySQLcmd.ExecuteNonQuery() <= 0)
                {
                    Console.WriteLine("Brugeren oprettes.");
                    new MySqlCommand(query_new, connection);
                }
                else
                {
                    Console.WriteLine("Brugeren opdateres");
                    new MySqlCommand(query_update, connection);
                }
            }
            catch
            {
            }
            Console.WriteLine("Done.");
            Console.ReadKey();
        }
    }
}

Den virker bare ikke som den skal. Lige meget om brugeren eksistere eller ikke, så skriver ExecuteNonQuery() "-1".

Men tilgengæld bliver der ikke oprettet noget.
Avatar billede Syska Mester
29. december 2010 - 17:25 #46
ja, det er forbi han ikke findes.

Nu er den der også snart ... med hjælp fra os (Da jeg ikke løser opgaver for folk, men hjælper dem på vej)

I din "if" sætning ... smider du dit command object lige efter du har oprettet det ... dvs du når aldrig at fyre det af mod din database.

Du skal nok gøre noget ala: (kan indeholde fejl)
                if (MySQLcmd.ExecuteNonQuery() <= 0)
                {
                    Console.WriteLine("Brugeren oprettes.");
                    MySQLcmd = new MySqlCommand(query_new, connection);
                }
                else
                {
                    Console.WriteLine("Brugeren opdateres");
                    MySQLcmd = new MySqlCommand(query_update, connection);
                }
MySQLcmd.ExecuteNonQuery();
Avatar billede GoofyDK Nybegynder
29. december 2010 - 17:37 #47
Nu er den der næsten, men den skriver stadig "-1", selv om brugeren findes. Og så derfor også, oprettes den igen og igen.

Er der en alternativ måde?
Avatar billede Syska Mester
29. december 2010 - 17:45 #48
Hvis den bliver ved med det, så er din første select jo defekt. Ergo den gør ikke hvad du regner med ...

Bruger Paramters ... meget nemmere at arbejde med.

Arne_v har vist nok lavet en artikel om det med MSSQL, men det er samme koncept.

mvh
Avatar billede Syska Mester
29. december 2010 - 17:51 #49
Du kan også prøve:

og så lave din sqlcommand om til
"SELECT COUNT(*) FROM test WHERE Brugernavn = '" + Brugernavn "'"

if((int)(MySQLcmd.ExecuteScalar()) == 0) // findes ikke
{}
else
{} // findes
Avatar billede GoofyDK Nybegynder
29. december 2010 - 18:14 #50
if((int)(MySQLcmd.ExecuteScalar()) == 0) - den kunne jeg ikke få til at virke. så jeg blev nød til at lave den om til string.

if(MySQLcmd.ExecuteScalar().ToString() == "0")
                {
                    Console.WriteLine("Brugeren findes ikke");
                }
                else
                {
                    Console.WriteLine("Brugeren findes");
                }

Men så virkede den også, men hvorfor virker den int så ikke? Og desuden, nu virker det som det skal. Tak :)
Avatar billede Syska Mester
29. december 2010 - 18:41 #51
ExcuteScalar burde returnere den først column i den første row ... mener jeg.

Og COUNT(*) mener jeg laver en interger ... så ved ikke lige hvorfor man ikke kan caste den til en int.

Kan ske det skal håndteres på en anden måde ... ved jeg ikke lige. Har ikke noget kode foran mig.

http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executescalar.aspx

Der bruger MS selv et eksemple som mit, måske du kan se en forskel, jeg kan i hvert fald ikke lige regne ud hvad der skulle være forskel, med mindre MySQL returnere noget andet.

Men glad for at det endelig er kommet til at virke, og det var dig selv der lavede løsningen i stedet for os :-) Det lærer man mest af.

mvh
Avatar billede GoofyDK Nybegynder
29. december 2010 - 19:17 #52
Jeg bruger bare den løsning, der er blevet fundet frem til.

Det var nu ikke kun mig der lavede den, men det var en hård kamp. For jeg havde prøvet at lave alle mulige ting, men intet ville det som jeg ville.

Men I skal tusind tak for hjælpen. Jeg ved så ment ikke hvem der skal have points, måske skal de deles?
Avatar billede arne_v Ekspert
29. december 2010 - 19:23 #53
Det duer ikke at COUNT(*) returneres som int.

Hvad nu hvis der er mere end 2.1 mia. rækker.

:-)

(int)(decimal)cmd.ExecuteScalar() == 0

eller

(int)(long)cmd.ExecuteScalar() == 0

bør virke.

Og faktisk er der ingen grund til det sidste cast, så

(decimal)cmd.ExecuteScalar() == 0

eller

(long)cmd.ExecuteScalar() == 0

Jeg kan ikke huske om det er decimal eller long den returnerer.
Avatar billede Syska Mester
29. december 2010 - 21:52 #54
Ja, men hvis han kommer over 2.1 millard brugere ... skal vi så ikke også blive enige om at han kunne kommer over 2^64 ?

Men den bør jo aldrig returnere mere end 1, da jeg har forstået på det hele at Brugernavn skal være unik.

http://msdn.microsoft.com/en-us/library/ms175997.aspx
Siger den returnere en INT ... i hvert fald i MSSQL

http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_count
Som du er inde på er en BIGINT alias long i .NET

Der kan man bare se ... jeg gik ud fra de gjorde det samme, men der har MS ikke været helt så smart ... og dog, default synes jeg bør være INT. :-)

Så svaret må være:
int til MSSQL
long til MySQL

mvh
Avatar billede arne_v Ekspert
29. december 2010 - 23:36 #55
Hvis en række inkl. overhead fylder 8 bytes, så vil 2^63 (signed !) rækker fylde 68719476736 GB, så det er ikke et problem lige med det første.

MS har også http://msdn.microsoft.com/en-us/library/ms190317.aspx, men det er sådan lidt af et workaround.

Det giver god mening at lade typen af COUNT være fast uanset den faktisk værdi, så selvom værdien kun er 0 eller 1, så har den jo den type som den har.
Avatar billede Syska Mester
31. december 2010 - 13:29 #56
Nej, på samme måde er det heller ikke et problem med COUNT. Da den enten giver 0 eller 1 ... giver den alt andet, så er der en bug i hans kode.

Jeg ved godt den har COUNT_BIG ... derfor jeg gik ud fra at MySQL var på samme måde.

Ja, derfor jeg også gik ud far den var fast og en cast til int i hans kode.

Men bottom line ... vi er enige :-)

Godt nytår.
Avatar billede GoofyDK Nybegynder
02. januar 2011 - 19:16 #57
Jeg tænkte på, om vi ikke skulle få lukket den her :-)
Lægger i svar, så er det perfekt. Endnu engang, tak for hjælpen.
Avatar billede arne_v Ekspert
02. januar 2011 - 21:04 #58
alle 3 formoder jeg?

svar fra mig
Avatar billede Syska Mester
03. januar 2011 - 17:18 #59
svar
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester