Netti: Mysql SET update

Hi, Hier mein kleines Problemchen ;o)

wenn sich ein Buchstabe schon in der Liste 1 befindet soll Liste 2 um 1-Zähler (Gleicher Index im Set) erhöht werden.

Beispiel: zwei Listen

Liste 1: a,b,c,d,e

Liste 2: 1,1,1,1,1

diese befinden sich jeweils in einer Mysql DB

jetzt möchte ich Liste 1 um f erweitern

UPDATE `abc` SET

liste1 = 
(CASE
WHEN FIND_IN_SET('f', liste1) = 0 
	THEN 
		CONCAT_WS(',',liste1,'f')
	ELSE liste1
END),

liste2 = 
(CASE
WHEN FIND_IN_SET('f', ) > 0 
	THEN 
		REPLACE(
                /*
                Hier komme ich nicht weiter 
                Liste zwei gleicher Set Index soll um eins erhöht werden 

                  */		
               +1)
				)
WHEN FIND_IN_SET('f', liste1) = 0 
	THEN 
		CONCAT_WS(',',liste2,'0')
END)

Vielen Dank Netti

  1. Tach!

    wenn sich ein Buchstabe schon in der Liste 1 befindet soll Liste 2 um 1-Zähler (Gleicher Index im Set) erhöht werden.

    Du hast da sozusagen eine eigene Art der Datenhaltung in einem DBMS, statt die Möglichkeiten des verwendeten DBMS direkt zu verwenden.

      REPLACE(
                /*
                Hier komme ich nicht weiter 
                Liste zwei gleicher Set Index soll um eins erhöht werden 
    
                  */		
               +1)
    

    So einfach geht das auch nicht. REPLACE() ersetzt Teilstrings. Es ist aber nicht gegeben, dass deine Zahlen einmalig sind, so dass man sie eindeutig finden könnte. REPLACE() ist also hier nicht brauchbar. Stattdessen musst du den String anhand der Trennzeichen auseinandernehmen, dann den Wert verändern und die Teile wieder zusammenfügen. Kann man sicherlich irgendwie (u.a. mit Hilfe von SUBSTRING_INDEX()) bewerkstelligen, aber schön geht anders.

    Wenn möglich ändere lieber das Tabellendesign. Das benötigt dann vermutlich eine Tabelle mehr, die Daten lassen sich dann aber einfacher bearbeiten.

    dedlfix.

    1. Vielen Dank für Deine Antwort!

      Genau das ist mein Problem REPLACE() ersetzt alles:

      REPLACE(
      		laenge_wort,
      		(SUBSTRING_INDEX(SUBSTRING_INDEX(liste2, ',', FIND_IN_SET('a', liste1)), ',', -1)),
      		((SUBSTRING_INDEX(SUBSTRING_INDEX(liste2, ',', FIND_IN_SET('a', liste1)), ',', -1))+1)
      				)
      

      Dennoch müsste es gehen...

      es wird ja eineindeutig die Stelle mit "FIND_IN_SET('a', liste1))" gefunden…

      In der Doku habe ich folgendes gefunden UPDATE tbl SET set_col = set_col & ~(1 << (FIND_IN_SET('TheValueToBeRemoved', set_col)-1)) WHERE ...

      also müsste es evtl. bitweise funktionieren

      1. Tach!

        Genau das ist mein Problem REPLACE() ersetzt alles:

        Ich sag ja, du kannst es nicht verwenden.

        REPLACE(
        		laenge_wort,
        		(SUBSTRING_INDEX(SUBSTRING_INDEX(liste2, ',', FIND_IN_SET('a', liste1)), ',', -1)),
        		((SUBSTRING_INDEX(SUBSTRING_INDEX(liste2, ',', FIND_IN_SET('a', liste1)), ',', -1))+1)
        				)
        

        Dennoch müsste es gehen...

        Aber nicht mit REPLACE(). Stattdessen den linken Teil abschneinden, den rechten auch, dann hast du einen mittleren Teil. Den bearbeiten und den linken, die Mitte und den rechten Teil wieder zusammensetzen.

        In der Doku habe ich folgendes gefunden UPDATE tbl SET set_col = set_col & ~(1 << (FIND_IN_SET('TheValueToBeRemoved', set_col)-1)) WHERE ...

        also müsste es evtl. bitweise funktionieren

        Nein, du hast kein bitweises SET, sondern einen kommaseparierten String. FIND_IN_SET() kann mit beidem arbeiten. Aber Bit-Operationen nützen dir nichts.

        dedlfix.

        1. Oh Mann, während ich meinen Sermon schreibe, diskutiert ihr euch schon die Finger wund :)

          Find_In_Set liefert Dir übrigens nicht die Position im String, sonder das wievielte Wort es ist. Auch wenn Du in liste1 nur Buchstaben hast - die 1:1 Korrelation zwischen den Buchstaben und Zählerpositionen geht beim 10. Auftreten eines Buchstabens kaputt.

          Und was ist beim Löschen von Buchstaben? Da wird es erst so richtig gruselig.

          Rolf

  2. Ein Set ist in MYSQL DB-Column eine maximal 64-bittige Bitmap, und die Werte, die Du im Set speichern kannst, werden bei der Tabellendeklaration festgelegt. FIND_IN_SET ist eigentlich dafür gedacht.

    Allerdings könntest Du mit einer SET Column dein liste2-Feld nicht repräsentieren, da ein Set nicht mehrmals den gleichen Wert aufnehmen kann. Deine Datenbank wird also keine SET Columns enthalten, sondern Strings. Was Du damit hast, sind zwei Columns, die jeweils ein serialisiertes Array sind.

    Eine Column mit strukturiertem Inhalt oder eine Wiederholungsgruppe von Columns sind etwas, was in einer relationalen DB nichts zu suchen hat. Den Grund dafür hast Du beim Stellen deiner Frage bemerkt: SQL ist dafür nicht gemacht. Bitte lies Dich in die Normalformen von Relationen ein - dein Konstrukt verletzt die erste Normalform.

    Wie macht man es besser?

    Vermutlich hat deine Tabelle eine identifizierende Spalte (ID-Nummer oder ein Name), liste1, liste2 und noch ein paar Spalten mehr.

    Ziehe liste1 und liste2 aus der Tabelle heraus und mach daraus eine neue Tabelle, mit den 3 Spalten

    • ID (oder was auch immer Du in der Haupt-tabelle zum eindeutigen Identifizieren von Rows verwendest)
    • Buchstabe CHAR(1)
    • Anzahl INT (oder TINYINT wenn du weißt dass es nicht viele werden)

    Als Primary Key dieser set_tabelle nimmst Du ID und Buchstabe.

    Nun hast Du zwar andere Aufgaben zu lösen (z.B. Unterscheiden zwischen INSERT und UPDATE), aber das sind Aufgaben die in die Domäne einer Datenbank gehören.

    Wenn Du nun für mehrere IDs das Set um ein 'f' erweitern willst, machst Du das mit MYSQL in etwa so:

    INSERT INTO set_tabelle (id, buchstabe, anzahl)
    SELECT ID, 'f', 1 FROM haupt_tabelle WHERE (bedingung für die gewünschten sätze)
    ON DUPLICATE KEY SET anzahl=anzahl+1
    

    Ohne die ON DUPLICATE KEY Klausel wäre es deutlich mühsamer, das ist ein nettes Gimmick in MySQL. In MS SQL gibt's noch den MERGE-Befehl, der ist aber deutlich schwergewichtiger.

    Natürlich belegt deine DB jetzt deutlich mehr Platz. Aber dafür operierst Du darauf nun auch ohne gebrochene Finger und vermutlich auch deutlich fixer.

    Rolf

    1. Herzlichen Dank an Alle für das Feedback!

      Offenbar ist es wirklich so das Mysql einen Zugriff auf SET Elemente per Index nicht kann.

      also muss ich mir meine Struktur nochmal überdenken.

      Netti

      1. Hello,

        Offenbar ist es wirklich so das Mysql einen Zugriff auf SET Elemente per Index nicht kann.

        also muss ich mir meine Struktur nochmal überdenken.

        Wenn den oder die Wert/e meinst, der/die zum jeweiligen "Index" (besser Bitcode) gehört/en, dann kannst Du die aus dem Imformation_Schema holen und mit Hilfe des Codes bestimmen.

        Liebe Grüße
        Tom S.

        --
        Es gibt nichts Gutes, außer man tut es
        Andersdenkende waren noch nie beliebt, aber meistens diejenigen, die die Freiheit vorangebracht haben.
        1. Hi Tom,

          Kannst Du mir das bitte mal zeigen .....

          https://dev.mysql.com/doc/refman/5.5/en/string-functions.html#function_make-set

          Die Funktion "make set" habe ich gefunden um auf Elemente zuzugreifen

          DANKE Netti

          1. Hello,

            Kannst Du mir das bitte mal zeigen .....

            Ich poste das mal als Ausschnitt hier:

            # Zerlegung der Table-Metadaten aus der MySQL-DB
            
            # returns:
            # info[fieldname1]['name'] 			Name der Spalte
            #  			  	  ['type']		1)	Datentyp laut Typenliste (1)
            #                 ['len']  		*)	Länge des Feldes gesamt
            #                 ['dec']  		*)	Dezimalstellen
            #                 ['typeext]  	*)	z.Zt. nur 'unsigned' bei N-Typen
            #                 ['values']  	*)	Werteliste  für enum und set
            #                 ['rel']  		*)	Relation zu welcher Tabelle table.field
            #                 ['null']      	Nullwerte erlaunt = 'YES'
            #                 ['key']        	Index-Typ: PRI, MUL, UNI
            #                 ['default']    	Defaultwert(e) für das Feld
            #                 ['extra']      	z.T. nur 'auto_increment' 
            #				  ['rem']			Comment der Spalte
            #				  ['charset']		Character-Set der Codierungung
            #				  ['collation']		Sortierung des Character-Sets	 
            #				  ['rights']		Rechte des abfragenden Users auf die Spalte
            #									- beachte: Delete-Recht steht erst in table_privileges, oder höher
            #
            # info[fieldname2][...]
            # ...
            #
            
            # 1) wird von mir zerlegt und *) werden hinzugefügt 
            
            # Typenliste (1)
            # Typen sind zur Zeit:
            #   tinyint
            #   smallint
            #   mediumint
            #   int
            #   bigint
            #   float
            #   double
            #   decimal
            #   date
            #   datetime
            #   timestamp
            #   time
            #   year
            #   char
            #   varchar
            #   tinyblob
            #   tinytext
            #   text
            #   blob
            #   mediumblob
            #   mediumtext
            #   longblob
            #   longtext
            #   enum
            #   set
            #   point
            #   linestring
            #   polygon
            #   geometry
            #   multipoint
            #   multilinestring
            #   multipolygon
            #   geometrycollection
            
            # Formatbeispiele:
            #   bigint(20) unsigned
            #   timestamp(14) (Ausgabeformat ersetzt durch YYYY-MM-DD H:i:s, 19 Zeichen)
            #   varchar(30)
            #   set('EINS','ZWEI','DREI','VIER','FÜNF')
            #   float(11,10)
            #   double(11,5)
            #   decimal(10,2)
            #   year(2)
            #   char(2)
            #   enum('eins','zwei','sieben','12')
            
            
            #--------------------------------------------------------------------
            
            function dbi_split_types($_rec)
            {
            	if(!is_array($_rec)) 				return false;
            	
            	## N  	= TypeClass Numeric
            	## NC 	= Typeclass Numeric Comma
            	## D  	= TypeClass Date
            	## S  	= TypeClass String
            	## SM 	= TypeClass String Memo/Multistring	
            	## SL 	= TypeClass String List
            	## SLM 	= TypeClass String List Multiple
            	## G	= Typeclass Geometry
              
            	## Zerlegen in Typbezeichnung und Spezifizierung
            	$pm = preg_match('~([a-z]+)(\([\'a-zßäöüA-ZÄÖÜ,0-9-_]*\))?([ a-z]+)?~', $_rec['COLUMN_TYPE'], $_matches);
            	if ($pm != 1) return false;    
            /*
            	if ($_matches[1] !== $_rec['DATA_TYPE'])
            	{
            		error_log('dbi_split_types(): Typenangaben stimmen nicht überein' . PHP_EOL);
            		return false;
            	}
            */	
            	$_spec = Array();
            	$_spec['name'] = $_rec['COLUMN_NAME'];		## 	
            	$_spec['type'] = $_rec['DATA_TYPE'];    	## int, timestamp, varchar, ...
            	$_spec['null'] = $_rec['IS_NULLABLE'];		## YES, NO
            	$_spec['default'] = $_rec['COLUMN_DEFAULT'];## wird ggf, aufgespalten als Array
            	$_spec['extra'] = $_rec['EXTRA'];	
            	$_spec['key'] = $_rec['COLUMN_KEY'];		## Index-Typ: PRI, MUL, UNI
            	if (false === ($_spec['rem'] = @unserialize($_rec['COLUMN_COMMENT'])))
            	{
            		$_spec['rem'] = $_rec['COLUMN_COMMENT'];	## nur über Information_Schema abfragbar
            	}	
            	$_spec['charset'] =	$_rec['CHARACTER_SET_NAME'];	## utf8, iso8859-1	
                $_spec['collation'] = $_rec['COLLATION_NAME'];		## Vergleich/Sortierung: utf8_general_ci
            	$_spec['rights'] =	$_rec['PRIVILEGES'];	## Rechte des abfragenden Users auf die Spalte: select,insert,update,references
            	
            	$specstr = null;
            	if (isset($_matches[2]))
            	{
            		## Spezifizierung auswerten
            #		$_spec['spec'] = $_matches[2];
            		## Klammern entfernen
            		$specstr = trim($_matches[2], '()');
            	}
            
            	if (isset($_matches[3]))
            	{
            		## Zusatzspezifizierung auswerten
            		$_spec['typeext'] = trim($_matches[3]);
            	}
            
            	switch ($_matches[1]) 
            	{
            		case 'tinyint':
            		case 'smallint':
            		case 'mediumint':
            		case 'int':		
            		case 'bigint':		
            			$_spec['typeclass'] = 'N';
            			$_spec['len'] = $specstr;
            		break;
            		
            		case 'float':
            		case 'double':
                    case 'decimal':
            			$_spec['typeclass'] = 'NC';
            			$_specdata = explode(',',$specstr);
            			$_spec['len'] = $_specdata[0];			
            			$_spec['dec'] = $_specdata[1];					
            		break;
            		
                    case 'timestamp':
            		case 'datetime':
            			$_spec['len'] = 19;		## Darstellung als 2015-01-07 17:48:11
            		case 'date':
            		case 'time':
            		case 'year':
            			$_spec['typeclass'] = 'D';
            		break;
            		
            		case 'char':
                    case 'varchar':
            			$_spec['typeclass'] = 'S';
            			$_spec['len'] = $specstr;
            		break;
            		
            		case 'binary':
            		case 'varbinary':
            		case 'tinyblob':
            		case 'tinytext':
            		case 'text':
            		case 'blob':
            		case 'mediumblob':
            		case 'mediumtext':
            		case 'longblob':
            		case 'longtext':
            			$_spec['typeclass'] = 'SM';
            		break;
            		
            		case 'enum':				## Ein Wert aus einer Liste
            			$_spec['typeclass'] = 'SL';		
            			## Default-Wert als Array darstellen, damit bei der Verarbeitung dieselbe Funktion 
            			## benutzt werden kann, wie bei 'set'
            			$_spec['default'] = explode(",", $_spec['default']);
            			## Führende und abschließende Apostophe entfernen
            			$specstr = trim($specstr,"'");
            			## Werte vereinzeln
            			$_spec['values'] = explode("','", $specstr);
            		break;	
            		
            		case 'set':					## Mehrere Werte aus einer Liste
            			$_spec['typeclass'] = 'SLM';
            			## Default-Werte als Array darstellen
            			$_spec['default'] = explode(",", $_spec['default']);
            			## Führende und abschließende Apostophe entfernen
            			$specstr = trim($specstr,"'");
            			## Werte vereinzeln
            			$_spec['values'] = explode("','", $specstr);
            		break;
            
            		case 'point':
            		case 'linestring':
            		case 'polygon':
            		case 'geometry':
            		case 'multipoint':
            		case 'multilinestring':
            		case 'multipolygon':
            		case 'geometrycollection':
            			$_spec['typeclass'] = 'G';
            		break;
            			
            		default:		
            			$_spec['typeclass'] = 'NA';
            	}
            	
            	return $_spec;	
            }
            
            #--------------------------------------------------------------------
            function dbi_get_info($con, $table)
            {
            	if (!is_object($con)) 
            	{
            		error_log('dbi_get_info(): keine gültige Connection' . PHP_EOL);
            		return false;
            	}		
            
            	if (strlen($table) == 0) return false;
            
            	$sql = "show columns from $table";
            	$sql = "
            		select `COLUMN_NAME`, `COLUMN_DEFAULT`, `IS_NULLABLE`, 
            			`DATA_TYPE`, `CHARACTER_MAXIMUM_LENGTH`, `COLUMN_KEY`, 
            			`NUMERIC_PRECISION`, `NUMERIC_SCALE`, `CHARACTER_SET_NAME`, 
            			`COLLATION_NAME`, `COLUMN_TYPE`, `COLUMN_COMMENT`,
            			`EXTRA`, `PRIVILEGES`
            		from `information_schema`.`columns` where `table_name` = '" . 
            		mysqli_real_escape_string($con, $table) . "'";
            
            	$res = mysqli_query($con, $sql);
            	if (!$res || (mysqli_num_rows($res) === 0)) 
            	{
            		error_log('dbi_get_info(): Abfrage auf Tabelle `'. $table . '` ohne Ergebnis. ' . mysqli_error($con) . PHP_EOL);
            		return false;
            	}	
            
            	$_info = array();
            	while ($_rec = mysqli_fetch_assoc($res))
            	{
            		if(!isset($_rec['COLUMN_NAME'], $_rec['COLUMN_TYPE'])) 	
            		{
            			error_log('dbi_split_types(): Keine Spaltendaten vorhanden' . PHP_EOL);
            			return false;
            		}	
            
            		$field = $_rec['COLUMN_NAME'];
            		$_info[$field] = dbi_split_types($_rec);
            	} 
            
            	return $_info;
            }   
            
            #--------------------------------------------------------------------
            
            echo "<pre>\r\n";
            echo htmlspecialchars(print_r(dbi_get_info($con, $table),1)) . "\r\n";
            echo "</pre>\r\n";
            
            

            Die $con und die $table musst Du angeben. Dann bekommst Du (hoffentlich, da ich es nur ausgeschnitten habe aus meiner Sammlung) ein Array mit der Struktur der Tabelle und weiteren Klassifiziernungen, die ich nützlich fand für die spätere Darstellung oder Abfrage mittels HTML.

            Liebe Grüße
            Tom S.

            --
            Es gibt nichts Gutes, außer man tut es
            Andersdenkende waren noch nie beliebt, aber meistens diejenigen, die die Freiheit vorangebracht haben.
            1. Hi Tom,

              das ist schick aber hilft bei der Fragestellung nicht so richtig weiter

              mysql> SELECT MAKE_SET(2,'a','b','c'); // liste 1 (!!! Unique !!!) mysql> SELECT MAKE_SET(2,'1','2','3'); // liste 2

              MAKE_SET aus Liste 1 greift eineindeutig auf den nennen wir es mal "INDEX im Set" zu … Da Liste 1 immer Unique ist könnte Liste 2 anhand des SET-Index aus Liste 1 ohne Verlust der Relation/Integrität manipuliert werden
              leider kennen ich keine Möglichkeit von MYSQL das praktikabel umzusetzen.

              zwischenzeitlich habe ich es über Datenbankstrukturen (Tabellen) gelöst.

              Ursprünglich wollte ich mit der oben genannten Konstruktion

              INSERT INTO ...… ON DUPLICATE KEY UPDATE ... hier der Update

              lösen um den Traffic des Datenbankservers zu reduzieren

              Netti

              1. Hello,

                das ist schick aber hilft bei der Fragestellung nicht so richtig weiter

                Das habe ich befürchtet, dass Du nicht die Essenz daraus ziehen wirst :-P

                Das Kernstatement ist eigentlich nur

                select `COLUMN_NAME`, `COLUMN_DEFAULT`, `IS_NULLABLE`, 
                			`DATA_TYPE`, `CHARACTER_MAXIMUM_LENGTH`, `COLUMN_KEY`, 
                			`NUMERIC_PRECISION`, `NUMERIC_SCALE`, `CHARACTER_SET_NAME`, 
                			`COLLATION_NAME`, `COLUMN_TYPE`, `COLUMN_COMMENT`,
                			`EXTRA`, `PRIVILEGES`
                		from `information_schema`.`columns` where `table_name` = '" [ ... ]
                
                

                Experimentiere doch einfach mal damit, welche Daten Du da schon mySQL-intern abgreifen kannst und direkt weiterverarbeiten kannst ohne in die API aufsteigen zu müssen.

                Liebe Grüße
                Tom S.

                --
                Es gibt nichts Gutes, außer man tut es
                Andersdenkende waren noch nie beliebt, aber meistens diejenigen, die die Freiheit vorangebracht haben.
              2. Es ist gut, wenn Du auf Tabellen umgestiegen bist. Es ist ein Irrglaube, dass man durch wilde Algorithmik innerhalb einer Row den SQL Server entlasten könnte. Die relationalen Operationen führt ein SQL Server sehr fix und optimiert durch, dafür wurde er ja gebaut. Alles, was darüber hinausgeht, ist typischerweise umständlich und langsam.

                Und übrigens: Der erste Parameter von MAKE_SET ist kein Index. Es ist eine Bitmap, mit der Du auswählst, welche der darauf folgenden Strings Du haben willst und welche nicht.

                MAKE_SET(13, 'a', 'b', 'c', 'd') liefert 'a,c,d' (weil 5 binär 1101 ist)
                MAKE_SET(41, 'a', 'schnurz', 'huhu', 'c', 'selfhtml', 'd') liefert ebenfalls 'a,c,d', wegen 41=101001b

                Für deine Begriffe (liste1) mag das irgendwie noch angehen. Für deine Zähler (liste2) sind die SET-Funktionen komplett ungeeignet.

                Rolf

      2. Viel wichtiger ist noch, dass sich deine Usage-Zähler nicht als Set abbilden lassen. Der Column-Typ SET ist daher nicht geeignet für Dich.

        Normalformen von Relationen - deine nächste Lektüre vor dem Redesign :)

        Rolf