[{"data":1,"prerenderedAt":1071},["ShallowReactive",2],{"blog-post-case-refactoring-sql-queries":3},{"id":4,"title":5,"body":6,"created":1062,"description":1063,"extension":1064,"isVisible":56,"meta":1065,"navigation":56,"path":1066,"seo":1067,"stem":1068,"tags":1069,"__hash__":1070},"blog\u002Fblog\u002Fcase-refactoring-sql-queries.md","Refactoring complicated SQL queries using damn simple query builder",{"type":7,"value":8,"toc":1055},"minimark",[9,14,18,21,287,290,294,299,302,305,664,667,670,674,677,680,730,738,798,801,1048,1051],[10,11,13],"h2",{"id":12},"problem","Problem",[15,16,17],"p",{},"An administrating application included an \"advanced search\" query for finding routes.\nThere were around 10 search criteria that could be combined together.\nAlso this query was using around 10 tables in the database.",[15,19,20],{},"Previous code was looking something like this before:",[22,23,28],"pre",{"className":24,"code":25,"language":26,"meta":27,"style":27},"language-java shiki shiki-themes github-dark","public class OperatorController {\n\n  @Autowired\n  private OperatorRepository operatorRepository;\n\n  public List\u003COperator> search() {\n    if (!nameInputField.isEmpty() && !classificationField.isEmpty() && !availabilityField.isEmpty()) {\n      return operatorRepository.findByNameAndClassificationAndAvailability(nameInputField,\n        classificationField, availabilityField);\n    } else if (!nameInputField.isEmpty() && !classificationField.isEmpty()) {\n      return operatorRepository.findByNameAndClassification(nameInputField, classificationField);\n    } else if (!nameInputField.isEmpty()) {\n      return operatorRepository.findByName(nameInputField);\n    } else {\n      return Collections.emptyList();\n    }\n  }\n}\n","java","",[29,30,31,51,58,67,76,81,102,148,163,169,201,214,233,246,255,269,275,281],"code",{"__ignoreMap":27},[32,33,36,40,43,47],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"snl16","public",[32,41,42],{"class":38}," class",[32,44,46],{"class":45},"svObZ"," OperatorController",[32,48,50],{"class":49},"s95oV"," {\n",[32,52,54],{"class":34,"line":53},2,[32,55,57],{"emptyLinePlaceholder":56},true,"\n",[32,59,61,64],{"class":34,"line":60},3,[32,62,63],{"class":49},"  @",[32,65,66],{"class":38},"Autowired\n",[32,68,70,73],{"class":34,"line":69},4,[32,71,72],{"class":38},"  private",[32,74,75],{"class":49}," OperatorRepository operatorRepository;\n",[32,77,79],{"class":34,"line":78},5,[32,80,57],{"emptyLinePlaceholder":56},[32,82,84,87,90,93,96,99],{"class":34,"line":83},6,[32,85,86],{"class":38},"  public",[32,88,89],{"class":49}," List\u003C",[32,91,92],{"class":38},"Operator",[32,94,95],{"class":49},"> ",[32,97,98],{"class":45},"search",[32,100,101],{"class":49},"() {\n",[32,103,105,108,111,114,117,120,123,126,129,132,134,136,138,140,143,145],{"class":34,"line":104},7,[32,106,107],{"class":38},"    if",[32,109,110],{"class":49}," (",[32,112,113],{"class":38},"!",[32,115,116],{"class":49},"nameInputField.",[32,118,119],{"class":45},"isEmpty",[32,121,122],{"class":49},"() ",[32,124,125],{"class":38},"&&",[32,127,128],{"class":38}," !",[32,130,131],{"class":49},"classificationField.",[32,133,119],{"class":45},[32,135,122],{"class":49},[32,137,125],{"class":38},[32,139,128],{"class":38},[32,141,142],{"class":49},"availabilityField.",[32,144,119],{"class":45},[32,146,147],{"class":49},"()) {\n",[32,149,151,154,157,160],{"class":34,"line":150},8,[32,152,153],{"class":38},"      return",[32,155,156],{"class":49}," operatorRepository.",[32,158,159],{"class":45},"findByNameAndClassificationAndAvailability",[32,161,162],{"class":49},"(nameInputField,\n",[32,164,166],{"class":34,"line":165},9,[32,167,168],{"class":49},"        classificationField, availabilityField);\n",[32,170,172,175,178,181,183,185,187,189,191,193,195,197,199],{"class":34,"line":171},10,[32,173,174],{"class":49},"    } ",[32,176,177],{"class":38},"else",[32,179,180],{"class":38}," if",[32,182,110],{"class":49},[32,184,113],{"class":38},[32,186,116],{"class":49},[32,188,119],{"class":45},[32,190,122],{"class":49},[32,192,125],{"class":38},[32,194,128],{"class":38},[32,196,131],{"class":49},[32,198,119],{"class":45},[32,200,147],{"class":49},[32,202,204,206,208,211],{"class":34,"line":203},11,[32,205,153],{"class":38},[32,207,156],{"class":49},[32,209,210],{"class":45},"findByNameAndClassification",[32,212,213],{"class":49},"(nameInputField, classificationField);\n",[32,215,217,219,221,223,225,227,229,231],{"class":34,"line":216},12,[32,218,174],{"class":49},[32,220,177],{"class":38},[32,222,180],{"class":38},[32,224,110],{"class":49},[32,226,113],{"class":38},[32,228,116],{"class":49},[32,230,119],{"class":45},[32,232,147],{"class":49},[32,234,236,238,240,243],{"class":34,"line":235},13,[32,237,153],{"class":38},[32,239,156],{"class":49},[32,241,242],{"class":45},"findByName",[32,244,245],{"class":49},"(nameInputField);\n",[32,247,249,251,253],{"class":34,"line":248},14,[32,250,174],{"class":49},[32,252,177],{"class":38},[32,254,50],{"class":49},[32,256,258,260,263,266],{"class":34,"line":257},15,[32,259,153],{"class":38},[32,261,262],{"class":49}," Collections.",[32,264,265],{"class":45},"emptyList",[32,267,268],{"class":49},"();\n",[32,270,272],{"class":34,"line":271},16,[32,273,274],{"class":49},"    }\n",[32,276,278],{"class":34,"line":277},17,[32,279,280],{"class":49},"  }\n",[32,282,284],{"class":34,"line":283},18,[32,285,286],{"class":49},"}\n",[15,288,289],{},"Different combinations were causing writing a unique query and adding a new \"if\"\nclause in code. Removing \u002F Adding search criteria was quite hard and took much\ntime.",[10,291,293],{"id":292},"refactoring","Refactoring",[295,296,298],"h3",{"id":297},"introduce-service-bean","Introduce service bean",[15,300,301],{},"The first thing that was clear to me was to separate controller from service logic.\nI tried to ban all repository beans from controllers and introduce a service bean\nbetween them. This way, I could list all operations on Operator domain object into\na single bean.",[15,303,304],{},"The method would look now like this:",[22,306,308],{"className":24,"code":307,"language":26,"meta":27,"style":27},"public class OperatorService {\n\n  private final OperatorRepository operatorRepository;\n\n  @Autowired\n  public OperatorService(final OperatorRepository operatorRepository) {\n    this.operatorRepository = operatorRepository;\n  }\n\n  public List\u003COperator> search(final Map&lt;String, Serializable&gt; fields) {\n    if (fields.contains(\"name\") && fields.contains(\"classification\") && fields.contains(\"availability\")) {\n      final Classification classification = (Classification) fields.get(\"classification\");\n      final Availability availability = (Availability) fields.get(\"availability\");\n      final String name = fields.get(\"name\").toString();\n      return operatorRepository.findByNameAndClassificationAndAvailability(name,\n        classification, availability);\n    } else if (fields.contains(\"name\") && fields.contains(\"classification\")) {\n      return operatorRepository.findByNameAndClassification(name, classification);\n    } else if (...) {\n      ....\n    } else {\n      ...\n    }\n  }\n\n}\n\n",[29,309,310,321,325,334,338,344,366,381,385,389,439,486,509,529,554,565,570,600,611,623,629,638,644,649,654,659],{"__ignoreMap":27},[32,311,312,314,316,319],{"class":34,"line":35},[32,313,39],{"class":38},[32,315,42],{"class":38},[32,317,318],{"class":45}," OperatorService",[32,320,50],{"class":49},[32,322,323],{"class":34,"line":53},[32,324,57],{"emptyLinePlaceholder":56},[32,326,327,329,332],{"class":34,"line":60},[32,328,72],{"class":38},[32,330,331],{"class":38}," final",[32,333,75],{"class":49},[32,335,336],{"class":34,"line":69},[32,337,57],{"emptyLinePlaceholder":56},[32,339,340,342],{"class":34,"line":78},[32,341,63],{"class":49},[32,343,66],{"class":38},[32,345,346,348,350,353,356,359,363],{"class":34,"line":83},[32,347,86],{"class":38},[32,349,318],{"class":45},[32,351,352],{"class":49},"(",[32,354,355],{"class":38},"final",[32,357,358],{"class":49}," OperatorRepository ",[32,360,362],{"class":361},"s9osk","operatorRepository",[32,364,365],{"class":49},") {\n",[32,367,368,372,375,378],{"class":34,"line":104},[32,369,371],{"class":370},"sDLfK","    this",[32,373,374],{"class":49},".operatorRepository ",[32,376,377],{"class":38},"=",[32,379,380],{"class":49}," operatorRepository;\n",[32,382,383],{"class":34,"line":150},[32,384,280],{"class":49},[32,386,387],{"class":34,"line":165},[32,388,57],{"emptyLinePlaceholder":56},[32,390,391,393,395,397,399,401,403,405,408,411,414,417,420,423,426,428,431,434,437],{"class":34,"line":171},[32,392,86],{"class":38},[32,394,89],{"class":49},[32,396,92],{"class":38},[32,398,95],{"class":49},[32,400,98],{"class":45},[32,402,352],{"class":49},[32,404,355],{"class":38},[32,406,407],{"class":361}," Map",[32,409,410],{"class":49},"&",[32,412,413],{"class":361},"lt",[32,415,416],{"class":49},";",[32,418,419],{"class":361},"String",[32,421,422],{"class":49},", ",[32,424,425],{"class":361},"Serializable",[32,427,410],{"class":49},[32,429,430],{"class":361},"gt",[32,432,433],{"class":49},"; ",[32,435,436],{"class":361},"fields",[32,438,365],{"class":49},[32,440,441,443,446,449,451,455,458,460,463,465,467,470,472,474,476,478,480,483],{"class":34,"line":203},[32,442,107],{"class":38},[32,444,445],{"class":49}," (fields.",[32,447,448],{"class":45},"contains",[32,450,352],{"class":49},[32,452,454],{"class":453},"sU2Wk","\"name\"",[32,456,457],{"class":49},") ",[32,459,125],{"class":38},[32,461,462],{"class":49}," fields.",[32,464,448],{"class":45},[32,466,352],{"class":49},[32,468,469],{"class":453},"\"classification\"",[32,471,457],{"class":49},[32,473,125],{"class":38},[32,475,462],{"class":49},[32,477,448],{"class":45},[32,479,352],{"class":49},[32,481,482],{"class":453},"\"availability\"",[32,484,485],{"class":49},")) {\n",[32,487,488,491,494,496,499,502,504,506],{"class":34,"line":216},[32,489,490],{"class":38},"      final",[32,492,493],{"class":49}," Classification classification ",[32,495,377],{"class":38},[32,497,498],{"class":49}," (Classification) fields.",[32,500,501],{"class":45},"get",[32,503,352],{"class":49},[32,505,469],{"class":453},[32,507,508],{"class":49},");\n",[32,510,511,513,516,518,521,523,525,527],{"class":34,"line":235},[32,512,490],{"class":38},[32,514,515],{"class":49}," Availability availability ",[32,517,377],{"class":38},[32,519,520],{"class":49}," (Availability) fields.",[32,522,501],{"class":45},[32,524,352],{"class":49},[32,526,482],{"class":453},[32,528,508],{"class":49},[32,530,531,533,536,538,540,542,544,546,549,552],{"class":34,"line":248},[32,532,490],{"class":38},[32,534,535],{"class":49}," String name ",[32,537,377],{"class":38},[32,539,462],{"class":49},[32,541,501],{"class":45},[32,543,352],{"class":49},[32,545,454],{"class":453},[32,547,548],{"class":49},").",[32,550,551],{"class":45},"toString",[32,553,268],{"class":49},[32,555,556,558,560,562],{"class":34,"line":257},[32,557,153],{"class":38},[32,559,156],{"class":49},[32,561,159],{"class":45},[32,563,564],{"class":49},"(name,\n",[32,566,567],{"class":34,"line":271},[32,568,569],{"class":49},"        classification, availability);\n",[32,571,572,574,576,578,580,582,584,586,588,590,592,594,596,598],{"class":34,"line":277},[32,573,174],{"class":49},[32,575,177],{"class":38},[32,577,180],{"class":38},[32,579,445],{"class":49},[32,581,448],{"class":45},[32,583,352],{"class":49},[32,585,454],{"class":453},[32,587,457],{"class":49},[32,589,125],{"class":38},[32,591,462],{"class":49},[32,593,448],{"class":45},[32,595,352],{"class":49},[32,597,469],{"class":453},[32,599,485],{"class":49},[32,601,602,604,606,608],{"class":34,"line":283},[32,603,153],{"class":38},[32,605,156],{"class":49},[32,607,210],{"class":45},[32,609,610],{"class":49},"(name, classification);\n",[32,612,614,616,618,620],{"class":34,"line":613},19,[32,615,174],{"class":49},[32,617,177],{"class":38},[32,619,180],{"class":38},[32,621,622],{"class":49}," (...) {\n",[32,624,626],{"class":34,"line":625},20,[32,627,628],{"class":49},"      ....\n",[32,630,632,634,636],{"class":34,"line":631},21,[32,633,174],{"class":49},[32,635,177],{"class":38},[32,637,50],{"class":49},[32,639,641],{"class":34,"line":640},22,[32,642,643],{"class":49},"      ...\n",[32,645,647],{"class":34,"line":646},23,[32,648,274],{"class":49},[32,650,652],{"class":34,"line":651},24,[32,653,280],{"class":49},[32,655,657],{"class":34,"line":656},25,[32,658,57],{"emptyLinePlaceholder":56},[32,660,662],{"class":34,"line":661},26,[32,663,286],{"class":49},[15,665,666],{},"Still the main trouble is this control structure that has no sense and feels\nlike a boilerplate code.",[15,668,669],{},"In the beginning I moved all fields into a simple map. If clauses were still\ninfecting the service bean, but I separated business logic from the UI at least",[295,671,673],{"id":672},"extensive-queries","Extensive queries",[15,675,676],{},"The main reason that I had to keep the \"if\" clauses were actual boundaries in the way\nwe were writing SQL queries with JPA and @Query annotations. After some research I\ncrossed upon the JPA Metamodel and criteria as a way to programmatically write\nall SQL queries",[15,678,679],{},"After some tests and experiments I noticed that JPA criteria were relatively complicated\nand not straight-forward. For fun and profit, I decided to create my own way on top of\nthis solution. My goal was to write something like this:",[22,681,683],{"className":24,"code":682,"language":26,"meta":27,"style":27},"select().from(OperatorEntity.class).where(OperatorEntity_.name)\n   .isEqualToIgnoreCase(\"test\")\n   .findOne();\n",[29,684,685,705,721],{"__ignoreMap":27},[32,686,687,690,693,696,699,702],{"class":34,"line":35},[32,688,689],{"class":45},"select",[32,691,692],{"class":49},"().",[32,694,695],{"class":45},"from",[32,697,698],{"class":49},"(OperatorEntity.class).",[32,700,701],{"class":45},"where",[32,703,704],{"class":49},"(OperatorEntity_.name)\n",[32,706,707,710,713,715,718],{"class":34,"line":53},[32,708,709],{"class":49},"   .",[32,711,712],{"class":45},"isEqualToIgnoreCase",[32,714,352],{"class":49},[32,716,717],{"class":453},"\"test\"",[32,719,720],{"class":49},")\n",[32,722,723,725,728],{"class":34,"line":60},[32,724,709],{"class":49},[32,726,727],{"class":45},"findOne",[32,729,268],{"class":49},[15,731,732,733,737],{},"On top of this builder, I was able to wrap it with a ",[734,735,736],"i",{},"QueryBuilder"," that checks nulls\nand dynamically builds the query accordingly",[22,739,741],{"className":24,"code":740,"language":26,"meta":27,"style":27},"SearchQueryBuilder\n  .fromFields(fields)\n  .resultingEntityClass(OperatorEntity.class)\n  .where(OperatorEntity_.name)\n  .isEqualToIgnoreCase(\"name\")\n  .findOne()\n",[29,742,743,748,759,769,777,789],{"__ignoreMap":27},[32,744,745],{"class":34,"line":35},[32,746,747],{"class":49},"SearchQueryBuilder\n",[32,749,750,753,756],{"class":34,"line":53},[32,751,752],{"class":49},"  .",[32,754,755],{"class":45},"fromFields",[32,757,758],{"class":49},"(fields)\n",[32,760,761,763,766],{"class":34,"line":60},[32,762,752],{"class":49},[32,764,765],{"class":45},"resultingEntityClass",[32,767,768],{"class":49},"(OperatorEntity.class)\n",[32,770,771,773,775],{"class":34,"line":69},[32,772,752],{"class":49},[32,774,701],{"class":45},[32,776,704],{"class":49},[32,778,779,781,783,785,787],{"class":34,"line":78},[32,780,752],{"class":49},[32,782,712],{"class":45},[32,784,352],{"class":49},[32,786,454],{"class":453},[32,788,720],{"class":49},[32,790,791,793,795],{"class":34,"line":83},[32,792,752],{"class":49},[32,794,727],{"class":45},[32,796,797],{"class":49},"()\n",[15,799,800],{},"After this refactoring, the final code was looking something like this:",[22,802,804],{"className":24,"code":803,"language":26,"meta":27,"style":27},"  public class OperatorService {\n\n    private final OperatorRepository operatorRepository;\n    private final OperatorEntityToOperatorConverter converter;\n\n    @Autowired\n    public OperatorService(final OperatorRepository operatorRepository,\n      final OperatorEntityToOperatorConverter converter) {\n      this.operatorRepository = operatorRepository;\n      this.converter = converter;\n    }\n\n    public List\u003COperator> search(final Map\u003CString, Serializable> fields) {\n      return SearchQueryBuilder.fromFields(fields)\n        .resultingEntityClass(OperatorEntity.class)\n        .where(OperatorEntity_.name).isEqualToIgnoreCase(\"name\")\n        .and(OperatorEntity_.classification).isEqualByObject(\"classification\")\n        .and(OperatorEntity_.availability).isEqualToObject(\"availability\")\n        .map(OperatorEntityToOperatorConverter::convert)\n        .find();\n    }\n  }\n\n",[29,805,806,816,820,829,838,842,849,867,879,890,902,906,910,941,952,961,978,997,1015,1031,1040,1044],{"__ignoreMap":27},[32,807,808,810,812,814],{"class":34,"line":35},[32,809,86],{"class":38},[32,811,42],{"class":38},[32,813,318],{"class":45},[32,815,50],{"class":49},[32,817,818],{"class":34,"line":53},[32,819,57],{"emptyLinePlaceholder":56},[32,821,822,825,827],{"class":34,"line":60},[32,823,824],{"class":38},"    private",[32,826,331],{"class":38},[32,828,75],{"class":49},[32,830,831,833,835],{"class":34,"line":69},[32,832,824],{"class":38},[32,834,331],{"class":38},[32,836,837],{"class":49}," OperatorEntityToOperatorConverter converter;\n",[32,839,840],{"class":34,"line":78},[32,841,57],{"emptyLinePlaceholder":56},[32,843,844,847],{"class":34,"line":83},[32,845,846],{"class":49},"    @",[32,848,66],{"class":38},[32,850,851,854,856,858,860,862,864],{"class":34,"line":104},[32,852,853],{"class":38},"    public",[32,855,318],{"class":45},[32,857,352],{"class":49},[32,859,355],{"class":38},[32,861,358],{"class":49},[32,863,362],{"class":361},[32,865,866],{"class":49},",\n",[32,868,869,871,874,877],{"class":34,"line":150},[32,870,490],{"class":38},[32,872,873],{"class":49}," OperatorEntityToOperatorConverter ",[32,875,876],{"class":361},"converter",[32,878,365],{"class":49},[32,880,881,884,886,888],{"class":34,"line":165},[32,882,883],{"class":370},"      this",[32,885,374],{"class":49},[32,887,377],{"class":38},[32,889,380],{"class":49},[32,891,892,894,897,899],{"class":34,"line":171},[32,893,883],{"class":370},[32,895,896],{"class":49},".converter ",[32,898,377],{"class":38},[32,900,901],{"class":49}," converter;\n",[32,903,904],{"class":34,"line":203},[32,905,274],{"class":49},[32,907,908],{"class":34,"line":216},[32,909,57],{"emptyLinePlaceholder":56},[32,911,912,914,916,918,920,922,924,926,929,931,933,935,937,939],{"class":34,"line":235},[32,913,853],{"class":38},[32,915,89],{"class":49},[32,917,92],{"class":38},[32,919,95],{"class":49},[32,921,98],{"class":45},[32,923,352],{"class":49},[32,925,355],{"class":38},[32,927,928],{"class":49}," Map\u003C",[32,930,419],{"class":38},[32,932,422],{"class":49},[32,934,425],{"class":38},[32,936,95],{"class":49},[32,938,436],{"class":361},[32,940,365],{"class":49},[32,942,943,945,948,950],{"class":34,"line":248},[32,944,153],{"class":38},[32,946,947],{"class":49}," SearchQueryBuilder.",[32,949,755],{"class":45},[32,951,758],{"class":49},[32,953,954,957,959],{"class":34,"line":257},[32,955,956],{"class":49},"        .",[32,958,765],{"class":45},[32,960,768],{"class":49},[32,962,963,965,967,970,972,974,976],{"class":34,"line":271},[32,964,956],{"class":49},[32,966,701],{"class":45},[32,968,969],{"class":49},"(OperatorEntity_.name).",[32,971,712],{"class":45},[32,973,352],{"class":49},[32,975,454],{"class":453},[32,977,720],{"class":49},[32,979,980,982,985,988,991,993,995],{"class":34,"line":277},[32,981,956],{"class":49},[32,983,984],{"class":45},"and",[32,986,987],{"class":49},"(OperatorEntity_.classification).",[32,989,990],{"class":45},"isEqualByObject",[32,992,352],{"class":49},[32,994,469],{"class":453},[32,996,720],{"class":49},[32,998,999,1001,1003,1006,1009,1011,1013],{"class":34,"line":283},[32,1000,956],{"class":49},[32,1002,984],{"class":45},[32,1004,1005],{"class":49},"(OperatorEntity_.availability).",[32,1007,1008],{"class":45},"isEqualToObject",[32,1010,352],{"class":49},[32,1012,482],{"class":453},[32,1014,720],{"class":49},[32,1016,1017,1019,1022,1025,1028],{"class":34,"line":613},[32,1018,956],{"class":49},[32,1020,1021],{"class":45},"map",[32,1023,1024],{"class":49},"(OperatorEntityToOperatorConverter",[32,1026,1027],{"class":38},"::",[32,1029,1030],{"class":49},"convert)\n",[32,1032,1033,1035,1038],{"class":34,"line":625},[32,1034,956],{"class":49},[32,1036,1037],{"class":45},"find",[32,1039,268],{"class":49},[32,1041,1042],{"class":34,"line":631},[32,1043,274],{"class":49},[32,1045,1046],{"class":34,"line":640},[32,1047,280],{"class":49},[15,1049,1050],{},"Much better, right?",[1052,1053,1054],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":27,"searchDepth":53,"depth":53,"links":1056},[1057,1058],{"id":12,"depth":53,"text":13},{"id":292,"depth":53,"text":293,"children":1059},[1060,1061],{"id":297,"depth":60,"text":298},{"id":672,"depth":60,"text":673},1521498900,"This is a description 3","md",{},"\u002Fblog\u002Fcase-refactoring-sql-queries",{"title":5,"description":1063},"blog\u002Fcase-refactoring-sql-queries",[],"XDosLUKC-4x06CyDgnyxf3Sdmv9AKLCw8K8K9q4_EDo",1786914024543]