wie kann ich einen vorhandenen primärschlüssel in sql azure ändern?
<p>ich möchte einen vorhandenen primärschlüssel in einer sql azure-tabelle ändern.<br> es hat derzeit eine spalte, und ich möchte eine weitere hinzufügen.</p><p>auf sql server 2008 war dies ein kinderspiel, nur in ssms, poof. erledigt. so sieht die pk aus, wenn ich sie von sql server aus skripte:</p><pre><code>alter table [dbo].[friend] add constraint [pk\_friend] primary key clustered ( [userid] asc, [id] asc ) </code></pre><p>wenn ich jedoch in sql azure versuche, das oben genannte auszuführen, schlägt dies natürlich fehl:</p><p><code>table 'friend' already has a primary key defined on it.</code></p><p>gut, also versuche ich, den schlüssel fallen zu lassen:</p><p><code>tables without a clustered index are not supported in this version of sql server. please create a clustered index and try again.</code></p><p>ok, also versuche ich, einen temporären clustered-index zu erstellen, um die pk zu löschen:</p><pre><code>create clustered index ix\_test on [friend] ([userid],[id]) </code></pre><p>was in ... resultiert: <code>cannot create more than one clustered index on table 'friend'. drop the existing clustered index 'pk\_friend' before creating another.</code></p><p>großartig, ein catch22-moment.</p><p>wie füge ich die userid-spalte zu meiner vorhandenen pk hinzu?</p>
<p>hinweis: ab azure sql database v12 gelten diese einschränkungen nicht mehr.</p><p>das ist kein "primärindex". es gibt so etwas wie einen "primärschlüssel" und es gibt auch so etwas wie einen "clustered index". unterschiedliche konzepte, oft verwirrt. lassen sie uns angesichts dieser unterscheidung die frage noch einmal betrachten:</p><p>q1) kann der clustered-index in einer sql azure-tabelle geändert werden?<br> a: ja. verwenden sie <code>with (drop\_existing=on)</code>:</p><pre><code>create table friend ( userid int not null, id int not null); go create clustered index cdxfriend on friend (userid, id); go create clustered index cdxfriend on friend (id, userid) with (drop\_existing=on); go </code></pre><p>f2) kann der clustered-index einer tabelle mit einer primärschlüsseleinschränkung geändert werden?<br> a: ja, wie oben, solange die primärschlüsseleinschränkung nicht über den clustered-index erzwungen wird:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key nonclustered (id)); create clustered index cdxfriend on friend (userid, id); go create clustered index cdxfriend on friend (id, userid) with (drop\_existing=on); go </code></pre><p>f3) kann die primärschlüsseleinschränkung einer tabelle geändert werden?<br> a: ja, solange die primäre einschränkung nicht über den clustered-index erzwungen wird:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key nonclustered (id)); go create clustered index cdxfriend on friend (userid, id); go alter table friend drop constraint pk\_friend; alter table friend add constraint pk\_friend primary key nonclustered (userid) go </code></pre><p>f4) kann der primärschlüssel einer tabelle geändert werden, wenn er über den clustered-index erzwungen wird?<br> a: ja, wenn die tabelle nie zeilen hatte:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key clustered (userid, id)); go alter table friend drop constraint pk\_friend; alter table friend add constraint pk\_friend primary key clustered (id, userid) go </code></pre><p>f5) kann der primärschlüssel einer tabelle geändert werden, wenn er über den clustered-index erzwungen wird, wenn die tabelle gefüllt ist?<br> a: nein. jede operation, die einen aufgefüllten clustered-index in einen heap konvertiert, wird in sql azure blockiert <em>, auch wenn die tabelle leer ist </em>:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key clustered (userid, id)); go insert into friend (userid) values (1); delete from friend; go alter table friend drop constraint pk\_friend; </code></pre><p>als randnotiz: die einschränkung kann geändert werden, wenn die tabelle <em> abgeschnitten ist </em>.</p><p>die problemumgehung zum ändern der pk-einschränkung einer aufgefüllten tabelle besteht darin, das gute alte <a href="http://msdn.microsoft.com/en-us/library/ms188351.aspx"><code>sp\_rename</code></a> trick:</p><pre><code>create table friend ( userid int not null, id int not null identity(1,1), constraint pk\_friend primary key clustered (userid, id)); go insert into friend (userid) values (1); go create table friendnew ( userid int not null, id int not null identity(1,1), constraint pk\_friend\_new primary key clustered (id, userid)); go set identity\_insert friendnew on; insert into friendnew (userid, id) select userid, id from friend; set identity\_insert friendnew off; go begin transaction exec sp\_rename 'friend', 'friendold'; exec sp\_rename 'friendnew', 'friend'; commit; go sp\_help 'friend'; </code></pre><p>das <code>sp\_rename</code> ansatz hat einige probleme, vor allem, dass berechtigungen für die tabelle während des umbenennens nicht übertragen werden, sowie fremdschlüsseleinschränkungen.</p>
<p>angenommen, sie verwenden ms sql server (was sie nicht sind, finden sie im <strong> update unten </strong>):</p><blockquote> <p>eine tabelle kann nur eine zeitstempelspalte haben. der wert in der timestamp-spalte wird jedes mal aktualisiert, wenn eine zeile mit einer timestamp-spalte eingefügt oder aktualisiert wird. diese eigenschaft macht eine timestamp-spalte zu einem schlechten kandidaten für schlüssel, insbesondere für primärschlüssel. bei jeder aktualisierung der zeile wird der zeitstempelwert und damit der schlüsselwert geändert. wenn sich die spalte in einem primärschlüssel befindet, ist der alte schlüsselwert nicht mehr gültig und fremdschlüssel, die auf den alten wert verweisen, sind nicht mehr gültig. wenn in einem dynamischen cursor auf die tabelle verwiesen wird, ändern alle aktualisierungen die position der zeilen im cursor. befindet sich die spalte in einem indexschlüssel, generieren alle aktualisierungen der datenzeile auch aktualisierungen des index.</p> </blockquote><p>informationen zu <a href="http://msdn.microsoft.com/en-us/library/aa260631%28v=sql.80%29.aspx"> msdn </a> </p><p>wenn sie datums-/uhrzeitinformationen für eine zeile speichern müssen und diese datums-/uhrzeitänderung nicht haben, verwenden sie datetime. andernfalls verwenden sie timestamp.</p><p><strong> beachten sie auch: </strong> ms sql server-zeitstempelfelder sind weder datum noch uhrzeit. sie sind binäre darstellungen der relativen reihenfolge, in der die daten geändert wurden.</p><h2>aktualisieren</h2><p>wie sie aktualisiert haben, um mysql zu sagen:</p><blockquote> <p>timestamp-werte werden zum speichern von der aktuellen zeitzone in utc konvertiert und zum abrufen von utc zurück in die aktuelle zeitzone konvertiert. (dies tritt nur für den timestamp-datentyp auf, nicht für andere typen wie datetime.)</p> </blockquote><p>zitat aus <a href="http://dev.mysql.com/doc/refman/5.0/en/timestamp.html">mysql-referenz</a> </p><p>insbesondere:</p><blockquote> <p>wenn sie einen timestamp-wert speichern, dann die zeitzone ändern und den wert abrufen, unterscheidet sich der abgerufene wert von dem von ihnen gespeicherten wert.</p> </blockquote><p>wenn sie also eine anwendung in mehreren zeitzonen verwenden und das datum und die uhrzeit benötigen, um die einstellungen der einzelnen benutzer widerzuspiegeln, verwenden sie timestamp. wenn sie unabhängig von der zeitzone konsistenz benötigen, verwenden sie datetime</p> <p>im allgemeinen ist der schnellste weg, eine tabelle zu duplizieren, einfach:</p><pre><code>create table table2 as select * from table1; </code></pre><p>parallele inserts sind möglicherweise schneller, jedoch nur mit einem sehr schnellen festplattensubsystem (wenn daten auf vielen laufwerken verschachtelt sind). andernfalls ist dies langsamer.</p><p>sobald sie mit dem ändern von <code>table2</code> fertig sind, kann es den neuen namen annehmen mit:</p><pre><code>begin; drop table table1; alter table table2 rename to table1; commit; </code></pre><p>der befehl <code>drop table</code> benötigt eine exklusive sperre, die sich auf gleichzeitige leser auf eine weise auswirkt, die sie vielleicht vorhersehen möchten:</p><ul> <li><code>drop</code> wartet, bis alle ausstehenden lesevorgänge anderer transaktionen in der tabelle abgeschlossen sind.</li> <li>jede neue transaktion, die in der zwischenzeit versucht, diese tabelle zu lesen, wird gewartet und schlägt dann fehl, da das ursprüngliche <code>table1</code> nicht mehr vorhanden ist. der fehler würde so aussehen: "beziehung mit oid <em>oid</em> konnte nicht geöffnet werden")</li> </ul><p>um das zweite problem zu vermeiden, können sie <code>table1</code> in <code>old\_table1</code> umbenennen, anstatt es zu löschen, und es dann erst später außerhalb der transaktion löschen, wenn diese leser damit fertig sind. die obige sequenz würde also werden:</p><pre><code>begin; alter table table1 rename to old\_table1; alter table table2 rename to table1; commit; ... drop table old\_table1; </code></pre> <p>es ist eine einschränkung des dbms - soweit ich weiß in allen. und das nicht nur beim hinzufügen einer spalte, sondern auch beim neuanordnen von spalten. wenn wir eine <code>unique</code> -einschränkung für <code>(a1, a2)</code> haben, können wir keine <code>foreign key</code> hinzufügen, die <code>references (a2, a1)</code>, es sei denn, es gibt eine eindeutige einschränkung für diese <code>(a2, a1)</code> das ist im wesentlichen redundant.</p><p>es wäre nicht besonders schwierig, dies als feature hinzuzufügen:</p><blockquote> <p>wenn es eine <code>unique</code> -einschränkung für <code>(a)</code> gibt, ist auch jede <code>(a, b, c, ..., z)</code> oder <code>(b,c, ...a, ...z)</code> kombination garantiert <code>unique</code>.</p> </blockquote><p>oder die verallgemeinerung:</p><blockquote> <p>wenn für <code>(a1, a2, ..., an)</code> eine <code>unique</code> -einschränkung besteht, ist auch jede <code>(a1, a2, ..., an, b1, b2, ..., bm)</code> - kombination oder neuanordnung <code>unique</code> garantiert.</p> </blockquote><p>es scheint, dass es nicht gefragt wurde oder nicht als hoch genug eingestuft wurde, um implementiert zu werden.</p><p>sie können jederzeit - im jeweiligen kanal - eine anfrage für die zu implementierende funktion stellen. oder implementieren sie es sogar selbst, wenn das dbms open source ist, wie postgres.</p> <p>ja, mysql kann (unter bestimmten bedingungen) einen index für die spalten in order by verwenden. mysql kann jedoch keinen index für <em> gemischt</em> asc, desc-reihenfolge nach (<code>select * from foo order by bar asc, pants desc</code>). wenn sie ihre anfrage und die anweisung create table teilen, können wir ihre frage genauer beantworten.</p><p>hinweise zum optimieren der order by-klauseln: <a href="http://dev.mysql.com/doc/refman/5.1/en/order-by-optimization.html">http://dev.mysql.com/doc/refman/5.1/en/order-by-optimization.html</a> </p><p><strong> bearbeiten 21.01.2012 08:53 </strong></p><p>es gab fragen zur quelle meiner aussage zur verwendung eines index mit gemischtem asc/desc im order by. aus der dokumentation <a href="http://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html">order by optimization</a> :</p><blockquote> <p>in einigen fällen kann mysql keine indizes verwenden, um order by aufzulösen, obwohl es weiterhin indizes verwendet, um die zeilen zu finden, die mit der where-klausel übereinstimmen. diese fälle umfassen folgendes:</p> <p>...</p> <p>sie mischen asc und desc:</p> <p>select * from t1 order by key\_part1 desc, key\_part2 asc;</p> <p>...</p> </blockquote><p>außerdem lautet das desc-schlüsselwort <a href="http://dev.mysql.com/doc/refman/5.5/en/create-index.html">bedeutungslos</a> in create index</p><blockquote> <p>eine index\_col\_name-spezifikation kann mit asc oder desc enden. diese schlüsselwörter sind für zukünftige erweiterungen zur angabe des speichers für aufsteigende oder absteigende indexwerte zulässig. derzeit werden sie analysiert, aber ignoriert. indexwerte werden immer in aufsteigender reihenfolge gespeichert.</p> </blockquote> <p>in ssms so etwas ausführen ...</p><pre><code>use master -- be sure that you're not on mydb alter database mydb set single\_user with rollback immediate drop database mydb; </code></pre> <p>ein fremdschlüssel bezieht sich auf den primärschlüssel einer anderen tabelle. wenn der datensatz mit dem fremdwort eingegeben wird, muss der entsprechende datensatz mit diesem primärschlüssel vorhanden sein. daher müssen sie immer mit dem einfügen in die tabellen auf der 1-seite der beziehung (der master- oder eltern- oder primärschlüsseltabelle) beginnen. dann können nur sie einen datensatz auf der n-seite der beziehung eingeben (in die detail- oder kind- oder fremdschlüsseltabelle).</p><p>reihenfolge: 1. eintrag, 2. schlüsselwort, 3. schlüsselwortpriorität.</p><p>wenn eine tabelle mehrere fremdschlüssel enthält, müssen zuerst die zugehörigen datensätze in allen anderen tabellen mit den entsprechenden primärschlüsseln eingegeben werden. zum beispiel vor der eingabe eines datensatzes in <code>senserestricted...</code> müssen die entsprechenden datensätze in <code>sense</code> und in <code>reading</code> eingefügt worden sein.</p> <p>wie die antwort von sqlrockstar zitiert</p><blockquote> <p>am besten für große, unsortierte eingänge.</p> </blockquote><p>jetzt,</p><ul> <li>aus dem users.displayname-index-scan (angenommen, nicht gruppiert) erhalten sie users.id (vorausgesetzt, gruppiert) = unsortiert</li> <li>sie scannen auch posts nach owneruserid = unsortiert</li> </ul><p>dies sind 2 ungeordnete eingänge.</p><p>ich würde einen index für die posts-tabelle auf owneruserid in betracht ziehen, einschließlich titel. dadurch wird eine reihenfolge auf einer seite der eingabe zum join + hinzugefügt, der den index abdeckt</p><pre><code>create index ix\_owneruserid on posts (owneruserid) include (title) </code></pre><p>möglicherweise stellen sie dann fest, dass der users.displayname-index nicht verwendet wird und stattdessen die pk scannt.</p> <p>so erhalten sie die liste der spalten in der standardreihenfolge ohne die fehlerhafte spalte:</p><pre><code>select string\_agg(quote\_ident(attname), ', ' order by attnum) from pg\_attribute where attrelid = 'myschema.mytable'::regclass and not attisdropped -- no dropped (dead) columns and attnum &gt; 0 -- no system columns and attname &lt;&gt; 'bad\_column' -- case sensitive! </code></pre><p>oder einfach <code>where attrelid = 'mytable'::regclass</code>, wenn sie darauf vertrauen, dass der suchpfad <a href="https://stackoverflow.com/questions/9067335/how-does-the-search-path-influence-identifier-resolution-and-the-current-schema/9067777#9067777"></a> in das richtige schema aufgelöst wird.</p><p> <a href="http://www.postgresql.org/docs/current/interactive/functions-string.html#functions-string-other"><code>quote\_ident()</code></a> fügt bei bedarf doppelte anführungszeichen hinzu.</p><p>ich habe die gleiche frage <a href="http://www.postgresql.org/message-id/flat/1180483885.536572.84550@q69g2000hsb.googlegroups.com#1180483885.536572.84550@q69g2000hsb.googlegroups.com">2007 auf pgsql-general</a> gestellt. es war damals postgres 8.2. süße erinnerungen ...</p><p>verbunden:</p><ul> <li> <a href="https://stackoverflow.com/a/24089729/939860">so überprüfen sie, ob eine tabelle in einem bestimmten schema vorhanden ist</a> </li> </ul> <h2>es kommt darauf an ™</h2><p>die fähigkeit, einen oder mehrere cursor zu umgehen, hängt davon ab, was in diesem cursor ausgeführt wird. ohne zu wissen, was darin vor sich geht, gibt es keine möglichkeit zu sagen. es kann sein, dass es keine problemumgehung gibt und sie eine zeilenweise verarbeitung durchführen müssen.</p><p>nachfolgend einige beispiele.</p><h2>funktioniert nicht in sets</h2><p>dieses beispiel ist das grundlegendste und ist einfach die tatsache, dass sie ihr gesamtes dataset oder teile ihres datasets auf einmal abfragen können, aber der cursor wurde erstellt und fragt die daten zeilenweise ab. übliche, durch die dies ersetzt werden kann, sind <code>join</code>, <code>cross apply</code>/<code>outer apply</code> und andere.</p><p><strong> betrachten sie den folgenden datensatz: </strong></p><pre><code>create table dbo.lotr(lotrid int, charactername varchar(255), val varchar(255)); create table dbo.lotrattributes(lotrattributeid int, lotrid int, attrval varchar(255)); insert into dbo.lotr(lotrid,charactername,val) values(1,'frodo','ring') ,(2,'gandalf','staff'); insert into dbo.lotrattributes(lotrid,lotrattributeid,attrval) values(1,1,'ringattribute1') ,(1,2,'ringattribute2') ,(1,3,'ringattribute3') ,(2,4,'staffattribute1') ,(2,5,'staffattribute2'); </code></pre><p>man könnte versuchen, jeden datensatz zu finden, und er stimmt separat überein, indem man die tabelle <code>lotr</code> durchläuft.</p><p><strong> cursor: </strong></p><pre><code>declare @lotrid int declare c cursor for select lotrid from dbo.lotr; open c fetch next from c into @lotrid; while @@fetch\_status = 0 begin select lotrattributeid from dbo.lotrattributes where lotrid = @lotrid; fetch next from c into @lotrid; end close c deallocate c </code></pre><p><strong> das ergebnis sind zwei ergebnismengen </strong></p><pre><code>lotrattributeid 1 2 3 lotrattributeid 4 5 </code></pre><p><strong> wenn dieses <code>inner join</code> verwendet wird, erhalten wir das gleiche ergebnis wie eine ergebnismenge. </strong></p><pre><code>select lotrattributeid from dbo.lotr l inner join dbo.lotrattributes la on l.lotrid = la.lotrid; lotrattributeid 1 2 3 4 5 </code></pre><hr><h2>string-manipulation</h2><p>häufig wird <code>for xml path('')</code> verwendet, um zeichenfolgenmanipulationen innerhalb von cursorn zu ersetzen.</p><p><strong> datensatz </strong></p><pre><code>create table dbo.lotr(lotrid int, charactername varchar(255), val varchar(255)); create table dbo.lotrattributes(lotrattributeid int, lotrid int, attrval varchar(255)); insert into dbo.lotr(lotrid,charactername,val) values(1,'frodo','ring'); insert into dbo.lotrattributes(lotrid,lotrattributeid,attrval) values(1,1,'ringattribute1') ,(1,2,'ringattribute2') ,(1,3,'ringattribute3'); </code></pre><p><strong> doppelcursor mit string-manipulation </strong></p><pre><code>declare @lotrid int, @charactername varchar(255), @val varchar(255) declare @lotrattributeid int, @attrval varchar(255) declare c cursor for select lotrid,charactername, val from dbo.lotr open c fetch next from c into @lotrid,@charactername,@val while @@fetch\_status = 0 begin set @charactername +='|'+ @val declare d cursor for select lotrattributeid, attrval from dbo.lotrattributes where lotrid = @lotrid open d fetch next from d into @lotrattributeid,@attrval while @@fetch\_status = 0 begin set @charactername +='['+@attrval+ '],' fetch next from d into @lotrattributeid,@attrval end close d deallocate d fetch next from c into @lotrid,@charactername,@val end close c deallocate c select left(@charactername,len(@charactername)-1); </code></pre><p><strong> ergebnis </strong></p><pre><code>(no column name) frodo|ring[ringattribute1],[ringattribute2],[ringattribute3], </code></pre><p><strong> entfernen der cursor mit for xml path ('') </strong></p><pre><code>select l.charactername +'|'+ l.val + (select stuff((select ','+quotename(attrval) from dbo.lotrattributes la where la.lotrid = l.lotrid for xml path('')), 1, 1, '')) from dbo.lotr l; </code></pre><h2>* *</h2><p>die eigentliche problemumgehung besteht darin, herauszufinden, warum die daten auf diese weise dargestellt werden, und die anwendung zu ändern, um sie nicht in diesem format zu benötigen, und sie irgendwo zu speichern.</p><p>wenn ihre hände gebunden sind, wäre dies das nächstbeste.</p><hr><h2>fügen sie die top-10-werte in eine temporäre tabelle ein, basierend auf den ids in einer anderen tabelle</h2><p><strong> daten </strong></p><p>create table dbo.sometable (inserttableid int, val varchar (255)); create table dbo.top10table (top10tableid int, inserttableid int, val varchar (255));</p><pre><code>insert into dbo.sometable(inserttableid,val) values(1,'bla') ,(2,'blabla'); insert into dbo.top10table(top10tableid,inserttableid,val) values(1,1,'wuw') ,(2,1,'wuw') ,(3,1,'wuw'); </code></pre><p><strong> cursor </strong></p><pre><code>create table #top10values(top10tableid int, inserttableid int, val varchar(255)) declare @inserttableid int; declare c cursor for select inserttableid from dbo.sometable; open c fetch next from c into @inserttableid; while @@fetch\_status =0 begin insert into #top10values(top10tableid,inserttableid,val) select top(10) top10tableid,inserttableid,val from dbo.top10table where inserttableid = @inserttableid order by top10tableid fetch next from c into @inserttableid; end close c deallocate c select * from #top10values; drop table #top10values; </code></pre><p><strong> ergebnis </strong></p><pre><code>top10tableid inserttableid val 1 1 wuw 2 1 wuw 3 1 wuw </code></pre><p><strong> ersetzen des cursors durch <code>cross apply</code> und ein <code>cte</code> </strong></p><pre><code>create table #top10values(top10tableid int, inserttableid int, val varchar(255)); ;with cte as ( select inserttableid from dbo.sometable ) insert into #top10values(top10tableid,inserttableid,val) select t1t.top10tableid,t1t.inserttableid,t1t.val from cte cross apply (select top (10) top10tableid,inserttableid,val from dbo.top10table t1t where t1t.inserttableid = cte.inserttableid ) t1t ; select * from #top10values; drop table #top10values; </code></pre><hr><h2>andere beispiele</h2><ul> <li>ein beispiel zum ersetzen eines cursors zur auswahl eines dynamischen satzes von artikeln pro lieferant mithilfe von <code>cross apply</code> <a href="https://dba.stackexchange.com/questions/100761/replacing-a-cursor-with-a-single-query-possible">hier</a> .</li> <li>ein beispiel für die verwendung von fensterfunktionen zum ersetzen eines cursors <a href="https://dba.stackexchange.com/questions/127083/improve-a-query-using-cursor-to-set-based-approach">hier</a> .</li> </ul><hr><p><strong> manchmal gibt es keine andere wahl </strong></p><p>wenn sie nicht in sätzen arbeiten können und zeilenweise arbeiten müssen, können sie den cursor trotzdem optimieren.</p><p>eine der größten änderungen bei der beschleunigung des cursors ist das hinzufügen von <code>local fast\_forward</code>.</p><pre><code>declare c cursor local fast\_forward for select lotrid from dbo.lotr </code></pre><p>schauen sie sich <a href="https://sqlperformance.com/2012/09/t-sql-queries/cursor-options">diesen blogpost</a> von @aaronbertrand an, wo er die möglichen leistungsunterschiede erklärt, wenn cursoreinstellungen wie <code>local</code> &amp; <code>fast\_forward</code> verwenden oder nicht.</p> <p>dein verständnis ist falsch. wenn kennwörter als stark gesalzener hash gespeichert sind, kann der administrator keine <em>guten</em> benutzerkennwörter finden, kann jedoch diejenigen finden, die in listen häufig verwendeter kennwörter aufgeführt sind, indem er den hash und das salt auf jedes kennwort auf dem kennwort anwendet liste und auf der suche nach einem match. es ist jedoch viel einfacher, wenn die gespeicherten passwörter nicht gesalzen sind, da sie sie in diesem fall nur einmal und nicht einmal pro benutzer ausführen müssen. dies <em>möglicherweise</em> zeigt an, dass die gespeicherten passwörter nicht gesalzen sind , was der best practice widerspricht.</p>