2010-07-10 7 views
8

Quelqu'un peut-il me diriger vers un algorithme de tri en javascript qui trierait de la même manière que SQL Server (pour les colonnes nvarchar/unicode)?Tri par Javascript pour correspondre au tri SQL Server

Pour référence, ma question précédente sur ce problème se trouve ici: SQL Server 2008 - different sort orders on VARCHAR vs NVARCHAR values

Plutôt que d'essayer de changer le comportement de tri sur le côté serveur, est-il possible que je peux correspondre à ce du côté client? Ma question précédente parlait spécifiquement des tirets dans les ordres de tri, mais je vais supposer qu'il y a un peu plus à faire que de simplement ignorer les tirets comme faisant partie de la sorte.

J'ai ajouté quelques cas d'utilisation supplémentaires ici pour mieux démontrer la question

Exemple de données comme triées de SQL Server (2008):

?test 
^&$Grails Found 
bags of Garbage 
Brochures distributed 
Calls Received 
exhibit visitors 
Exhibit Visitors 
-Exhibit Visitors 
--Exhibit Visitors 
Ëxhibit Visitors 
Grails Found 

Comment puis-je obtenir le javascript pour trier les mêmes valeurs de la même façon?

S'il vous plaît laissez-moi savoir si je peux clarifier davantage.

+0

Ainsi, à partir de cette question, vous voulez le code JavaScript maintenant Unicode sorte '' A' avant Unicode -a'? –

+0

@Brock - correct, mais plus précisément, je veux un algorithme de tri javascript qui correspondra à celui du côté serveur (j'imagine qu'il y a plus à considérer que les caractères "-") – DanP

Répondre

6

Première Quelle est la collation de votre base de données? Je vais supposer que c'est SQL_Latin1_General_CP1_CS_AS ou SQL_Latin1_General_CP1_CI_AS. Si oui, alors ce qui suit devrait fonctionner (pas encore entièrement testé, pour l'instant).

Cela ressemble à écrire true Le trieur Unicode est une entreprise majeure. J'ai vu des codes fiscaux plus simples que les spécifications. ;-) Il semble toujours impliquer une ou plusieurs tables de recherche et au moins un tri à 3 niveaux - avec modification des caractères et des contractions à prendre en compte.

J'ai limité ce qui suit au Latin 1, Latin Extended-A et Latin Extended-B tables/collation. L'algorithme devrait fonctionner assez bien sur ces ensembles, mais je ne l'ai pas entièrement testé ni correctement pris en compte pour la modification des caractères (pour gagner en rapidité et en complexité).

Voir in action at jsbin.com.

Fonction:

function bIgnoreForPrimarySort (iCharCode) 
{ 
    /*--- A bunch of characters get ignored for the primary sort weight. 
     The most important ones are the hyphen and apostrophe characters. 
     A bunch of control characters and a couple of odds and ends, make up 
     the rest. 
    */ 
    if (iCharCode < 9)             return true; 

    if (iCharCode >= 14 && iCharCode <= 31)       return true; 

    if (iCharCode >= 127 && iCharCode <= 159)       return true; 

    if (iCharCode == 39 || iCharCode == 45 || iCharCode == 173) return true; 

    return false; 
} 


function SortByRoughSQL_Latin1_General_CP1_CS_AS (sA, sB) 
{ 
    /*--- This Sorts Latin1 and extended Latin1 unicode with an approximation 
     of SQL's SQL_Latin1_General_CP1_CS_AS collation. 
     Certain modifying characters or contractions my be off (not tested), we trade-off 
     perfect accuracy for speed and relative simplicity. 

     True unicode sorting is devilishly complex and we're not getting paid enough to 
     fully implement it in Javascript. ;-) 

     It looks like a definative sort would require painstaking exegesis of documents 
     such as: http://unicode.org/reports/tr10/ 
    */ 
    //--- This is the master lookup table for Latin1 code-points. Here through the extended set \u02AF 
    //--- Make this static? 
    var aSortOrder = [ 
        -1, 151, 152, 153, 154, 155, 156, 157, 158, 2, 3, 4, 5, 6, 159, 160, 161, 162, 163, 164, 
        165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 0, 7, 8, 9, 10, 11, 12, 210, 
        13, 14, 15, 41, 16, 211, 17, 18, 65, 69, 71, 74, 76, 77, 80, 81, 82, 83, 19, 20, 
        42, 43, 44, 21, 22, 214, 257, 266, 284, 308, 347, 352, 376, 387, 419, 427, 438, 459, 466, 486, 
        529, 534, 538, 559, 576, 595, 636, 641, 647, 650, 661, 23, 24, 25, 26, 27, 28, 213, 255, 265, 
        283, 307, 346, 350, 374, 385, 418, 426, 436, 458, 464, 485, 528, 533, 536, 558, 575, 594, 635, 640, 
        646, 648, 660, 29, 30, 31, 32, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 
        190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 
         1, 33, 53, 54, 55, 56, 34, 57, 35, 58, 215, 46, 59, 212, 60, 36, 61, 45, 72, 75, 
        37, 62, 63, 64, 38, 70, 487, 47, 66, 67, 68, 39, 219, 217, 221, 231, 223, 233, 250, 276, 
        312, 310, 316, 318, 392, 390, 395, 397, 295, 472, 491, 489, 493, 503, 495, 48, 511, 599, 597, 601, 
        603, 652, 590, 573, 218, 216, 220, 230, 222, 232, 249, 275, 311, 309, 315, 317, 391, 389, 394, 396, 
        294, 471, 490, 488, 492, 502, 494, 49, 510, 598, 596, 600, 602, 651, 589, 655, 229, 228, 227, 226, 
        235, 234, 268, 267, 272, 271, 270, 269, 274, 273, 286, 285, 290, 287, 324, 323, 322, 321, 314, 313, 
        326, 325, 320, 319, 358, 357, 362, 361, 356, 355, 364, 363, 378, 377, 380, 379, 405, 404, 403, 402, 
        401, 400, 407, 406, 393, 388, 417, 416, 421, 420, 432, 431, 428, 440, 439, 447, 446, 444, 443, 442, 
        441, 450, 449, 468, 467, 474, 473, 470, 469, 477, 484, 483, 501, 500, 499, 498, 507, 506, 527, 526, 
        540, 539, 544, 543, 542, 541, 561, 560, 563, 562, 567, 566, 565, 564, 580, 579, 578, 577, 593, 592, 
        611, 610, 609, 608, 607, 606, 613, 612, 617, 616, 615, 614, 643, 642, 654, 653, 656, 663, 662, 665, 
        664, 667, 666, 574, 258, 260, 262, 261, 264, 263, 281, 278, 277, 304, 292, 289, 288, 297, 335, 337, 
        332, 348, 349, 369, 371, 382, 415, 409, 434, 433, 448, 451, 462, 476, 479, 509, 521, 520, 524, 523, 
        531, 530, 552, 572, 571, 569, 570, 583, 582, 581, 585, 632, 631, 634, 638, 658, 657, 669, 668, 673, 
        677, 676, 678, 73, 79, 78, 680, 644, 50, 51, 52, 40, 303, 302, 301, 457, 456, 455, 482, 481, 
        480, 225, 224, 399, 398, 497, 496, 605, 604, 626, 625, 620, 619, 624, 623, 622, 621, 334, 241, 240, 
        237, 236, 254, 253, 366, 365, 360, 359, 430, 429, 505, 504, 515, 514, 675, 674, 422, 300, 299, 298, 
        354, 353, 84, 85, 86, 87, 239, 238, 252, 251, 513, 512, 243, 242, 245, 244, 328, 327, 330, 329, 
        411, 410, 413, 412, 517, 516, 519, 518, 547, 546, 549, 548, 628, 627, 630, 629, 88, 89, 90, 91, 
        92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 
        112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 
        132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 246, 247, 248, 259, 279, 280, 293, 291, 
        339, 336, 338, 331, 340, 341, 342, 423, 367, 373, 351, 370, 372, 383, 381, 384, 408, 414, 386, 445, 
        453, 452, 454, 461, 463, 460, 475, 478, 465, 508, 522, 525, 532, 550, 553, 554, 555, 545, 556, 557, 
        537, 551, 568, 333, 424, 343, 344, 586, 584, 618, 633, 637, 639, 645, 659, 649, 670, 671, 672, 679, 
        681, 682, 683, 282, 686, 256, 345, 368, 375, 425, 435, 437, 535, 684, 685, 305, 296, 306, 591, 587, 
        588, 144, 145, 146, 147, 148, 149, 150 
        ]; 

    var iLenA   = sA.length, iLenB   = sB.length; 
    var jA    = 0,   jB    = 0; 
    var sIgnoreBuff_A = [],   sIgnoreBuff_B = []; 


    function iSortIgnoreBuff() 
    { 
     var iIgLenA = sIgnoreBuff_A.length, iIgLenB = sIgnoreBuff_B.length; 
     var kA  = 0,     kB  = 0; 

     while (kA < iIgLenA && kB < iIgLenB) 
     { 
      var igA = sIgnoreBuff_A [kA++], igB = sIgnoreBuff_B [kB++]; 

      if (aSortOrder[igA] > aSortOrder[igB]) return 1; 
      if (aSortOrder[igA] < aSortOrder[igB]) return -1; 
     } 
     //--- All else equal, longest string loses 
     if (iIgLenA > iIgLenB)  return 1; 
     if (iIgLenA < iIgLenB)  return -1; 

     return 0; 
    } 


    while (jA < iLenA && jB < iLenB) 
    { 
     var cA = sA.charCodeAt (jA++); 
     var cB = sB.charCodeAt (jB++); 

     if (cA == cB) 
     { 
      continue; 
     } 

     while (bIgnoreForPrimarySort (cA)) 
     { 
      sIgnoreBuff_A.push (cA); 
      if (jA < iLenA) 
       cA = sA.charCodeAt (jA++); 
      else 
       break; 
     } 
     while (bIgnoreForPrimarySort (cB)) 
     { 
      sIgnoreBuff_B.push (cB); 
      if (jB < iLenB) 
       cB = sB.charCodeAt (jB++); 
      else 
       break; 
     } 

     /*--- Have we reached the end of one or both strings, ending on an ignore char? 
      The strings were equal, up to that point. 
      If one of the strings is NOT an ignore char, while the other is, it wins. 
     */ 
     if (bIgnoreForPrimarySort (cA)) 
     { 
      if (! bIgnoreForPrimarySort (cB)) return -1; 
     } 
     else if (bIgnoreForPrimarySort (cB)) 
     { 
      return 1; 
     } 
     else 
     { 
      if (aSortOrder[cA] > aSortOrder[cB]) 
       return 1; 

      if (aSortOrder[cA] < aSortOrder[cB]) 
       return -1; 

      //--- We are equal, so far, on the main chars. Where there ignore chars? 
      var iBuffSort = iSortIgnoreBuff(); 
      if (iBuffSort) return iBuffSort; 

      //--- Still here? Reset the ignore arrays. 
      sIgnoreBuff_A = []; 
      sIgnoreBuff_B = []; 
     } 

    } //-- while (jA < iLenA && jB < iLenB) 

    /*--- We have gone through all of at least one string and they are still both 
     equal barring ignore chars or unequal lengths. 
    */ 
    var iBuffSort = iSortIgnoreBuff(); 
    if (iBuffSort) return iBuffSort; 

    //--- All else equal, longest string loses 
    if (iLenA > iLenB)  return 1; 
    if (iLenA < iLenB)  return -1; 

    return 0; 

} //-- function SortByRoughSQL_Latin1_General_CP1_CS_AS 

test:

var aPhrases = [ 
        'Grails Found', 
        '--Exhibit Visitors', 
        '-Exhibit Visitors', 
        'Exhibit Visitors', 
        'Calls Received', 
        'Ëxhibit Visitors', 
        'Brochures distributed', 
        'exhibit visitors', 
        'bags of Garbage', 
        '^&$Grails Found', 
        '?test' 
       ]; 

aPhrases.sort (SortByRoughSQL_Latin1_General_CP1_CS_AS); 

console.log (aPhrases.join ('\n')); 

Résultats:

?test 
^&$Grails Found 
bags of Garbage 
Brochures distributed 
Calls Received 
exhibit visitors 
Exhibit Visitors 
-Exhibit Visitors 
--Exhibit Visitors 
Ëxhibit Visitors 
Grails Found 
+0

J'ai vérifié que le classement du serveur est configuré sur: SQL_Latin1_General_CP1_CI_AS, j'examinerai votre méthode pour voir comment elle fonctionne. En passant, je pense que j'étais un peu bon marché avec la prime ... si cela fonctionne, je vais laisser expirer avant d'accepter votre réponse afin que je puisse vous en attribuer un plus élevé (ce qui semble juste/raisonnable?) – DanP

+0

@ DanP: Ne vous inquiétez pas de la prime (à moins que vous n'obteniez pas une réponse satisfaisante sans un). J'aime les points, mais je fais aussi ces choses pour aider et comme un défi - au lieu de, disons, le Sudoku ou un mot croisé. –

+0

cela me semble génial! – Patricia

2

Désolé, JavaScript n'a pas été collation. La seule comparaison de chaîne que vous obtenez est directement sur les unités de code UTF-16 dans un String, tel que renvoyé par charCodeAt().

Pour les caractères dans le plan multilingue de base, c'est la même chose qu'un classement binaire, donc si vous avez besoin de JS et SQL Server pour être d'accord (en ignorant les plans astraux), je pense que c'est la seule façon il. (Short de construction d'une assembleuse de chaîne dans JS et la copie méticuleusement les règles de classement de SQL Server, de toute façon. Pas beaucoup de plaisir là-bas.)

(Quel est le cas d'utilisation, pourquoi ont-ils besoin de faire correspondre?)

+1

merci pour les idées; Le cas d'utilisation est assez simple: je renvoie des données triées depuis le serveur SQL et j'ai des capacités de tri côté client dans une table. Quand ils ne sont pas d'accord, j'ai des problèmes lors de la pagination, etc. – DanP

2

@BrockAdams' answer est grand, mais j'ai eu quelques cas de pointe avec des traits d'union au milieu de la chaîne qui ne correspond pas avec serveur SQL, je ne pouvais pas tout à fait savoir où il allait mal, donc je a écrit une version plus fonctionnelle qui filtre juste les caractères ignorés, puis compare les tableaux basés sur les points de code latin.

C'est probablement moins performant, mais il y a moins de code à comprendre et cela fonctionne sur les cas de tests SQL que j'ai ajoutés ci-dessous. J'utilisais une base de données SQL Server avec Latin1_General_100_CI_AS, donc il était insensible à la casse, mais j'ai gardé le code ici pour être sensible à la casse, il est assez facile de passer à la vérification insensible à la casse, en créant un wrapper fonction appliquant toLowerCase aux variables.

Il n'y avait pas de différence dans le tri entre les deux classements avec les cas de test que j'avais.

/** 
 
* This is a modified version of sortByRoughSQL_Latin1_General_CP1_CS_AS 
 
* This has a more functional approach, it is more basic 
 
* It simply does a character filter and then sort 
 
* @link https://stackoverflow.com/a/3266430/327074 
 
* 
 
* @param {String} a 
 
* @param {String} b 
 
* @returns {Number} -1,0,1 
 
*/ 
 
function latinSqlSort(a, b) { 
 
    'use strict'; 
 
    //--- This is the master lookup table for Latin1 code-points. 
 
    // Here through the extended set \u02AF 
 
    var latinLookup = [ 
 
     -1,151,152,153,154,155,156,157,158, 2, 3, 4, 5, 6,159,160,161,162,163,164, 
 
     165,166,167,168,169,170,171,172,173,174,175,176, 0, 7, 8, 9, 10, 11, 12,210, 
 
     13, 14, 15, 41, 16,211, 17, 18, 65, 69, 71, 74, 76, 77, 80, 81, 82, 83, 19, 20, 
 
     42, 43, 44, 21, 22,214,257,266,284,308,347,352,376,387,419,427,438,459,466,486, 
 
     529,534,538,559,576,595,636,641,647,650,661, 23, 24, 25, 26, 27, 28,213,255,265, 
 
     283,307,346,350,374,385,418,426,436,458,464,485,528,533,536,558,575,594,635,640, 
 
     646,648,660, 29, 30, 31, 32,177,178,179,180,181,182,183,184,185,186,187,188,189, 
 
     190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209, 
 
      1, 33, 53, 54, 55, 56, 34, 57, 35, 58,215, 46, 59,212, 60, 36, 61, 45, 72, 75, 
 
     37, 62, 63, 64, 38, 70,487, 47, 66, 67, 68, 39,219,217,221,231,223,233,250,276, 
 
     312,310,316,318,392,390,395,397,295,472,491,489,493,503,495, 48,511,599,597,601, 
 
     603,652,590,573,218,216,220,230,222,232,249,275,311,309,315,317,391,389,394,396, 
 
     294,471,490,488,492,502,494, 49,510,598,596,600,602,651,589,655,229,228,227,226, 
 
     235,234,268,267,272,271,270,269,274,273,286,285,290,287,324,323,322,321,314,313, 
 
     326,325,320,319,358,357,362,361,356,355,364,363,378,377,380,379,405,404,403,402, 
 
     401,400,407,406,393,388,417,416,421,420,432,431,428,440,439,447,446,444,443,442, 
 
     441,450,449,468,467,474,473,470,469,477,484,483,501,500,499,498,507,506,527,526, 
 
     540,539,544,543,542,541,561,560,563,562,567,566,565,564,580,579,578,577,593,592, 
 
     611,610,609,608,607,606,613,612,617,616,615,614,643,642,654,653,656,663,662,665, 
 
     664,667,666,574,258,260,262,261,264,263,281,278,277,304,292,289,288,297,335,337, 
 
     332,348,349,369,371,382,415,409,434,433,448,451,462,476,479,509,521,520,524,523, 
 
     531,530,552,572,571,569,570,583,582,581,585,632,631,634,638,658,657,669,668,673, 
 
     677,676,678, 73, 79, 78,680,644, 50, 51, 52, 40,303,302,301,457,456,455,482,481, 
 
     480,225,224,399,398,497,496,605,604,626,625,620,619,624,623,622,621,334,241,240, 
 
     237,236,254,253,366,365,360,359,430,429,505,504,515,514,675,674,422,300,299,298, 
 
     354,353, 84, 85, 86, 87,239,238,252,251,513,512,243,242,245,244,328,327,330,329, 
 
     411,410,413,412,517,516,519,518,547,546,549,548,628,627,630,629, 88, 89, 90, 91, 
 
     92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, 
 
     112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131, 
 
     132,133,134,135,136,137,138,139,140,141,142,143,246,247,248,259,279,280,293,291, 
 
     339,336,338,331,340,341,342,423,367,373,351,370,372,383,381,384,408,414,386,445, 
 
     453,452,454,461,463,460,475,478,465,508,522,525,532,550,553,554,555,545,556,557, 
 
     537,551,568,333,424,343,344,586,584,618,633,637,639,645,659,649,670,671,672,679, 
 
     681,682,683,282,686,256,345,368,375,425,435,437,535,684,685,305,296,306,591,587, 
 
     588,144,145,146,147,148,149,150 
 
    ]; 
 

 
    /** 
 
    * A bunch of characters get ignored for the primary sort weight. 
 
    * The most important ones are the hyphen and apostrophe characters. 
 
    * A bunch of control characters and a couple of odds and ends, make up 
 
    * the rest. 
 
    * 
 
    * @param {Number} 
 
    * @returns {Boolean} 
 
    * @link https://stackoverflow.com/a/3266430/327074 
 
    */ 
 
    function ignoreForPrimarySort(iCharCode) { 
 
     if (iCharCode < 9) { 
 
      return true; 
 
     } 
 

 
     if (iCharCode >= 14 && iCharCode <= 31) { 
 
      return true; 
 
     } 
 

 
     if (iCharCode >= 127 && iCharCode <= 159) { 
 
      return true; 
 
     } 
 

 
     if (iCharCode == 39 || iCharCode == 45 || iCharCode == 173) { 
 
      return true; 
 
     } 
 

 
     return false; 
 
    } 
 

 
    // normal sort 
 
    function compare(a, b) { 
 
     return a === b ? 0 : a > b ? 1 : -1; 
 
    } 
 

 
    // compare two arrays return first compare difference 
 
    function arrayCompare(a, b) { 
 
     return a.reduce(function (acc, x, i) { 
 
      return acc === 0 && i < b.length ? compare(x, b[i]) : acc; 
 
     }, 0); 
 
    } 
 

 
    /** 
 
    * convert a string to array of latin code point ordering 
 
    * @param {String} x 
 
    * @returns {Array} integer array 
 
    */ 
 
    function toLatinOrder(x) { 
 
     return x.split('') 
 
      // convert to char codes 
 
      .map(function(x){return x.charCodeAt(0);}) 
 
      // filter out ignored characters 
 
      .filter(function(x){return !ignoreForPrimarySort(x);}) 
 
      // convert to latin order 
 
      .map(function(x){return latinLookup[x];}); 
 
    } 
 

 
    // convert inputs 
 
    var charA = toLatinOrder(a), 
 
     charB = toLatinOrder(b); 
 

 
    // compare the arrays 
 
    var charsCompare = arrayCompare(charA, charB); 
 
    if (charsCompare !== 0) { 
 
     return charsCompare; 
 
    } 
 

 
    // fallback to the filtered array length 
 
    var charsLenCompare = compare(charA.length, charB.length); 
 
    if (charsLenCompare !== 0) { 
 
     return charsLenCompare; 
 
    } 
 

 
    // Final fallback to a basic length comparison 
 
    return compare(a.length, b.length); 
 
} 
 

 
var tests = [ 
 
    'Grails Found', 
 
    '--Exhibit Visitors', 
 
    '-Exhibit Visitors', 
 
    'Exhibit Visitors', 
 
    'Calls Received', 
 
    'Ëxhibit Visitors', 
 
    'Brochures distributed', 
 
    'exhibit visitors', 
 
    'bags of Garbage', 
 
    '^&$Grails Found', 
 
    '?test', 
 
    '612C-520', 
 
    '612-C-122', 
 
    '612C-122 I', 
 
    '612-C-126 L', 
 
    '612C-301 B', 
 
    '612C-304 B', 
 
    '612C-306', 
 
    '612-C-306', 
 
    '612-C-306 2', 
 
    '612-C-403 H', 
 
    '612C403 O', 
 
    '612-C-403(V)', 
 
    '612E-306A/B I', 
 
    '612E-306A/B O', 
 
    '612C-121 O', 
 
    '612C-111 B', 
 
    '- -612C-111 B' 
 
].sort(latinSqlSort).join('<br>'); 
 

 
document.write(tests);

+0

Je ne suis pas sûr que la valeur de '- -612C-111 B' est correctement triée, mais dans l'ensemble cette réponse semble bonne (ne pas vouloir revenir sur ce problème avec la rigueur requise pour l'instant). –

+1

@BrockAdams C'était en fait l'un des cas qui m'a envoyé dans ce trou de lapin. J'ai vérifié contre SQL Server - voici un [SQL Fiddle] (http://sqlfiddle.com/#!18/3195a/2) du genre. – icc97