简体   繁体   中英

PHP usort() inconsistent result

I have created a PHP procedure which extract some table names from the database, and then sorts them based on custom algorithm.

Here is the gist of it:

function getTables($like) {
  // executes query "SHOW TABLES LIKE '$like%'" and return array
}

function tableSort(array $tables) {
  usort($tables, "compareTables");
  return $tables;
}

function compareTables($ta, $tb) {
    global $db;

    // foreign key test
    foreach(array(
        // if table A references table B, A is "greater than" B
        array(
            'TABLE' => $ta,
            'REFERENCED' => $tb,
            'VALUE' => 100
        ),
        // if table B references table A, A is "smaller than" B
        array(
            'TABLE' => $tb,
            'REFERENCED' => $ta,
            'VALUE' => -100
        ),
    ) as $test) {
        $res = $db->query("SELECT COUNT(*)
                           FROM information_schema.REFERENTIAL_CONSTRAINTS fk
                           WHERE fk.CONSTRAINT_SCHEMA = 'my_db'
                             AND fk.TABLE_NAME = '{$test['TABLE']}'
                             AND fk.REFERENCED_TABLE_NAME = '{$test['REFERENCED']}'")->field();

        if($res > 0) {
            return $test['VALUE'];
        }
    }

    // se non vi sono chiavi esterne, applico conteggio campi ID
    return countIds($ta) - countIds($tb);
}

function countIds($table) {
    global $db;

    // extract fields
    $fields = $db->query("DESCRIBE $table");

    // count ID fields
    $count = 0;

    // Listo per il numero di tutti i campi presenti nella tabella
    while($field = $fields->fetch()) {
        if (
           $field['Field'] == 'revisione_documento'
           || substr($field['Field'], 0, 3) == "id_"
        ) {
            ++$count;

            if($field['Field'] == 'revisione_documento') {
                // stop counting now
                break;
            }
        }
    }

    return $count;
}

however, I have found a couple of cases where the final sorted array has table_x before table_y even if compareTables('table_x', 'table_y') > 0 , so that I would expect those to be sorted differently.

Is it an issue with my sorting algorithm, or rather with usort() itself?

Here is an actual, albeit quite long, example:

// UNSORTED array
array (
  0 => 'lin_98_3',
  58 => 'lin_98_3_anamnesi_abitudini',
  59 => 'lin_98_3_anamnesi_allergologica',
  60 => 'lin_98_3_anamnesi_familiare',
  61 => 'lin_98_3_anamnesi_fisiologica',
  62 => 'lin_98_3_anamnesi_generale',
  63 => 'lin_98_3_anamnesi_ginecologica',
  64 => 'lin_98_3_anamnesi_handicap',
  65 => 'lin_98_3_anamnesi_lavorativa',
  66 => 'lin_98_3_anamnesi_lavorativa_lavori',
  67 => 'lin_98_3_anamnesi_lavorativa_rischi',
  68 => 'lin_98_3_anamnesi_patologica_familiare',
  69 => 'lin_98_3_anamnesi_patologica_remota',
  70 => 'lin_98_3_anamnesi_sportiva',
  71 => 'lin_98_3_anamnesi_stupefacenti',
  72 => 'lin_98_3_cartelle_cliniche',
  73 => 'lin_98_3_dipendenti',
  74 => 'lin_98_3_dipendenti_prescrizioni',
  75 => 'lin_98_3_dosp',
  76 => 'lin_98_3_esame_clinico',
  77 => 'lin_98_3_esame_clinico_capo_collo',
  78 => 'lin_98_3_esame_clinico_cardiovascolare',
  79 => 'lin_98_3_esame_clinico_cute_mucose',
  80 => 'lin_98_3_esame_clinico_digerente',
  81 => 'lin_98_3_esame_clinico_endocrino',
  82 => 'lin_98_3_esame_clinico_generale',
  83 => 'lin_98_3_esame_clinico_muscolo_scheletrico',
  84 => 'lin_98_3_esame_clinico_nervoso',
  85 => 'lin_98_3_esame_clinico_osteoarticolare',
  86 => 'lin_98_3_esame_clinico_respiratorio',
  87 => 'lin_98_3_esame_clinico_tegumentario',
  88 => 'lin_98_3_esame_clinico_uditivo',
  89 => 'lin_98_3_esame_clinico_urogenitale',
  90 => 'lin_98_3_liste',
  91 => 'lin_98_3_liste_dipendenti',
  92 => 'lin_98_3_malattie',
  93 => 'lin_98_3_malattie_assenze',
  94 => 'lin_98_3_malattie_visite',
  95 => 'lin_98_3_medici_curanti',
  96 => 'lin_98_3_protocolli',
  97 => 'lin_98_3_protocolli_agenti',
  98 => 'lin_98_3_protocolli_attrezzi',
  99 => 'lin_98_3_protocolli_dipendenti',
  100 => 'lin_98_3_protocolli_haccp_alimenti',
  101 => 'lin_98_3_protocolli_impianti',
  102 => 'lin_98_3_protocolli_macchine',
  103 => 'lin_98_3_protocolli_mansioni',
  104 => 'lin_98_3_protocolli_operazioni',
  105 => 'lin_98_3_protocolli_opere_provvisionali',
  106 => 'lin_98_3_protocolli_processi',
  107 => 'lin_98_3_protocolli_rischi',
  108 => 'lin_98_3_protocolli_vani',
  109 => 'lin_98_3_protocolli_visite',
  110 => 'lin_98_3_relazioni',
  111 => 'lin_98_3_sgs_visite',
  112 => 'lin_98_3_sgs_visite_parametri',
  113 => 'lin_98_3_sgs_visite_prescrizioni',
  114 => 'lin_98_3_studi_medici',
  115 => 'lin_98_3_vaccinazioni',
  116 => 'lin_98_3_vaccinazioni_somministrazioni',
  117 => 'lin_98_3_visite',
  118 => 'lin_98_3_visite_dipendenti',
)

// SORTED array
array (
  0 => 'lin_98_3',
  1 => 'lin_98_3_esame_clinico',
  2 => 'lin_98_3_esame_clinico_generale',
  3 => 'lin_98_3_esame_clinico_muscolo_scheletrico',
  4 => 'lin_98_3_esame_clinico_nervoso',
  5 => 'lin_98_3_esame_clinico_endocrino',
  6 => 'lin_98_3_esame_clinico_digerente',
  7 => 'lin_98_3_esame_clinico_cardiovascolare',
  8 => 'lin_98_3_esame_clinico_cute_mucose',
  9 => 'lin_98_3_esame_clinico_osteoarticolare',
  10 => 'lin_98_3_esame_clinico_respiratorio',
  11 => 'lin_98_3_protocolli',
  12 => 'lin_98_3_relazioni',
  13 => 'lin_98_3_studi_medici',
  14 => 'lin_98_3_medici_curanti',
  15 => 'lin_98_3_liste',
  16 => 'lin_98_3_esame_clinico_capo_collo',
  17 => 'lin_98_3_esame_clinico_uditivo',
  18 => 'lin_98_3_esame_clinico_urogenitale',
  19 => 'lin_98_3_visite',
  20 => 'lin_98_3_esame_clinico_tegumentario',
  21 => 'lin_98_3_anamnesi_ginecologica',
  22 => 'lin_98_3_anamnesi_generale',
  23 => 'lin_98_3_anamnesi_fisiologica',
  24 => 'lin_98_3_anamnesi_familiare',
  25 => 'lin_98_3_anamnesi_lavorativa',
  26 => 'lin_98_3_anamnesi_abitudini',
  27 => 'lin_98_3_dipendenti',
  28 => 'lin_98_3_protocolli_opere_provvisionali',
  29 => 'lin_98_3_protocolli_processi',
  30 => 'lin_98_3_visite_dipendenti',
  31 => 'lin_98_3_protocolli_macchine',
  32 => 'lin_98_3_protocolli_mansioni',
  33 => 'lin_98_3_protocolli_operazioni',
  34 => 'lin_98_3_protocolli_rischi',
  35 => 'lin_98_3_protocolli_vani',
  36 => 'lin_98_3_sgs_visite',
  37 => 'lin_98_3_sgs_visite_parametri',
  38 => 'lin_98_3_sgs_visite_prescrizioni',
  39 => 'lin_98_3_vaccinazioni',
  40 => 'lin_98_3_protocolli_visite',
  41 => 'lin_98_3_protocolli_impianti',
  42 => 'lin_98_3_anamnesi_allergologica',
  43 => 'lin_98_3_protocolli_attrezzi',
  44 => 'lin_98_3_cartelle_cliniche',
  45 => 'lin_98_3_anamnesi_patologica_familiare',
  46 => 'lin_98_3_anamnesi_lavorativa_lavori',
  47 => 'lin_98_3_anamnesi_patologica_remota',
  48 => 'lin_98_3_anamnesi_stupefacenti',
  49 => 'lin_98_3_dipendenti_prescrizioni',
  50 => 'lin_98_3_protocolli_haccp_alimenti',
  51 => 'lin_98_3_anamnesi_sportiva',
  52 => 'lin_98_3_anamnesi_handicap',
  53 => 'lin_98_3_dosp',
  54 => 'lin_98_3_malattie',
  55 => 'lin_98_3_protocolli_dipendenti',
  56 => 'lin_98_3_protocolli_agenti',
  57 => 'lin_98_3_malattie_visite',
  58 => 'lin_98_3_liste_dipendenti',
  59 => 'lin_98_3_malattie_assenze',
  60 => 'lin_98_3_vaccinazioni_somministrazioni',
  61 => 'lin_98_3_anamnesi_lavorativa_rischi',
)

however, the table lin_98_3_esame_clinico (index 1 in sorted array) should come after lin_98_3_dipendenti (index 27 ), since compareTables('lin_98_3_esame_clinico', 'lin_98_3_dipendenti') == 1

Finally, here is the structure for lin_98_3_esame_clinico and lin_98_3_dipendenti :

CREATE TABLE IF NOT EXISTS `lin_98_3_dipendenti` (
  `id_dipendente` int(10) unsigned NOT NULL DEFAULT '0',
  `id_azienda` int(10) unsigned NOT NULL DEFAULT '1',
  `id_sede` int(10) unsigned NOT NULL DEFAULT '1',
  `revisione_documento` int(10) unsigned NOT NULL DEFAULT '0',
  -- countIds() stops here!
  `id_sgs_medico` int(10) unsigned DEFAULT NULL,
  `sgs_medico` varchar(255) DEFAULT NULL,
  `esito` enum('Idoneo','Idoneo con prescrizioni','Idoneità parziale temporanea','Idoneità parziale permanente','Inidoneità temporanea','Inidoneità permanente','Sorveglianza sanitaria dopo cessazione esposizione') DEFAULT NULL,
  `note` text,
  `dosp_medico` varchar(255) DEFAULT NULL,
  `dosp_esito` enum('Idoneo','Idoneo con prescrizioni','Inidoneità permanente','Sorveglianza sanitaria dopo cessazione esposizione') DEFAULT NULL,
  `dosp_note` text,
  `dosp_categoria` enum('Non esposto','Categoria A','Categoria B') DEFAULT NULL,
  `flag_servizio_militare` tinyint(1) DEFAULT '0',
  `flag_lavoro_notturno` tinyint(1) DEFAULT '0',
  `attivita_extralavorative` set('Volontariato','Primo soccorso','Attività venatoria','Agricoltura/allevamento','Altro') DEFAULT NULL,
  `grado_studio` enum('Nessuno','Licenza elementare','Licenza media','Diploma superiore','Laurea') DEFAULT NULL,
  `medico_curante` varchar(255) DEFAULT NULL,
  `indirizzo_medico` varchar(255) DEFAULT NULL,
  `telefono_medico` varchar(255) DEFAULT NULL,
  `email_medico` varchar(255) DEFAULT NULL,
  `id_ultima_visita` int(10) unsigned DEFAULT NULL,
  `dosp_id_ultima_visita` int(10) unsigned DEFAULT NULL,
  `id_cartella_clinica` int(10) unsigned DEFAULT NULL,
  `flag_modifiche` tinyint(1) DEFAULT '0',
  PRIMARY KEY (`id_dipendente`,`id_azienda`,`id_sede`,`revisione_documento`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `lin_98_3_esame_clinico` (
  `id_esame_clinico` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `id_cartella_clinica` int(10) unsigned DEFAULT '0',
  `id_azienda` int(10) unsigned NOT NULL DEFAULT '1',
  `id_sede` int(10) unsigned NOT NULL DEFAULT '1',
  `revisione_documento` int(10) unsigned NOT NULL DEFAULT '0',
  -- countIds() stops here!
  PRIMARY KEY (`id_esame_clinico`,`id_azienda`,`id_sede`,`revisione_documento`),
  UNIQUE KEY `unique_id_cartella_clinica_lin_98_3_esame_clinico` (`id_cartella_clinica`,`id_azienda`,`id_sede`,`revisione_documento`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

for these two tables, countIds('lin_98_3_dipendenti') == 4 and countIds('lin_98_3_esame_clinico') == 5 , and there is no foreign key relation between these two.

Also, lin_98_3_cartelle_cliniche (index 44 ) should be before lin_98_3_anamnesi_generale (index 22 ), in fact there is a foreign key in lin_98_3_anamnesi_generale that references lin_98_3_anamnesi_generale and compareTables('lin_98_3_anamnesi_generale', 'lin_98_3_cartelle_cliniche') == 100

Your sort function is essentially wrong at least in the aspect it does not form a proper order. There might be cycles, ie, two tables referring to each other, in which case your function returns 100 both for (A,B) and for (B,A).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM