[{"data":1,"prerenderedAt":1760},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fsplit-vector-layer-by-attribute-pyqgis":3},{"id":4,"title":5,"body":6,"description":1749,"extension":1750,"meta":1751,"navigation":244,"path":1756,"seo":1757,"stem":1758,"__hash__":1759},"docs\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fsplit-vector-layer-by-attribute-pyqgis\u002Findex.md","Split a Vector Layer by Attribute in PyQGIS",{"type":7,"value":8,"toc":1735},"minimark",[9,13,17,26,188,193,210,214,217,337,362,369,373,376,780,813,820,824,827,988,1008,1120,1124,1127,1356,1361,1364,1368,1371,1379,1383,1386,1470,1483,1486,1490,1496,1584,1588,1635,1639,1645,1649,1655,1670,1681,1697,1701,1731],[10,11,5],"h1",{"id":12},"split-a-vector-layer-by-attribute-in-pyqgis",[14,15,16],"p",{},"Splitting one layer into many by the value of a field is the standard preparation step before a batch: one file per district for a set of deliverables, one per survey date for a time series, one per operator for a data-sharing agreement. The built-in algorithm does it in a line; writing the loop yourself takes ten and buys you control over the filenames, which is usually what the downstream process actually needs.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002F","Vector Data Manipulation in PyQGIS",". It covers the algorithm and its filename behaviour, a hand-written split that names files predictably, splitting into one GeoPackage with many layers, and the traps around null values and characters that are illegal in paths.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,97,102,106,114,117,125,130,138,144,147,151,155,159,163,167,172,177,182],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 320","img","One layer split by a district field into separate outputs, either as many files in a directory or as many layers inside one GeoPackage","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Two shapes the split can take",[40,41,42],"desc",{},"One input layer with a district attribute is grouped by that field's distinct values. The result can be written as one file per value in a directory, which suits handing folders to other people, or as one layer per value inside a single GeoPackage, which keeps the set together and shares one spatial reference definition.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","320","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"splArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Same grouping, two very different deliverables",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"20","94","180","130","10","#eff3ff","#2563eb","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"110","122","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","parcels.gpkg",[68,93,96],{"x":88,"y":94,"style":95,"fill":66,"textAnchor":74},"148","text-anchor:middle;font-size:10.5px;font-family:sans-serif","48 200 features",[68,98,101],{"x":88,"y":99,"style":100,"fill":66,"textAnchor":74},"172","text-anchor:middle;font-size:10.5px;font-family:monospace","district",[68,103,105],{"x":88,"y":104,"style":95,"fill":66,"textAnchor":74},"196","12 distinct values",[107,108],"line",{"x1":109,"y1":110,"x2":111,"y2":112,"stroke":66,"style":113},"204","140","242","104","stroke-width:2;marker-end:url(#splArrow)",[107,115],{"x1":109,"y1":80,"x2":111,"y2":116,"stroke":66,"style":113},"216",[44,118],{"x":119,"y":120,"width":121,"height":122,"rx":82,"fill":123,"stroke":124,"style":85},"250","56","486","106","#edf8e9","#15803d",[68,126,129],{"x":127,"y":128,"style":90,"fill":124,"textAnchor":74},"493","82","one file per value",[44,131],{"x":132,"y":133,"width":134,"height":135,"rx":59,"fill":136,"stroke":124,"style":137},"272","96","132","46","#e8efe6","stroke-width:1.6",[68,139,143],{"x":140,"y":141,"style":142,"fill":66,"textAnchor":74},"338","124","text-anchor:middle;font-size:10px;font-family:monospace","north.gpkg",[44,145],{"x":146,"y":133,"width":134,"height":135,"rx":59,"fill":136,"stroke":124,"style":137},"414",[68,148,150],{"x":149,"y":141,"style":142,"fill":66,"textAnchor":74},"480","central.gpkg",[44,152],{"x":153,"y":133,"width":154,"height":135,"rx":59,"fill":136,"stroke":124,"style":137},"556","160",[68,156,158],{"x":157,"y":141,"style":142,"fill":66,"textAnchor":74},"636","… 10 more",[44,160],{"x":119,"y":80,"width":121,"height":122,"rx":82,"fill":161,"stroke":162,"style":85},"#fdf2e2","#b45309",[68,164,166],{"x":127,"y":165,"style":90,"fill":162,"textAnchor":74},"206","one layer per value, one file",[44,168],{"x":132,"y":169,"width":170,"height":135,"rx":59,"fill":171,"stroke":162,"style":137},"220","444","#fffdf7",[68,173,176],{"x":174,"y":175,"style":142,"fill":66,"textAnchor":74},"494","240","districts.gpkg",[68,178,181],{"x":174,"y":179,"style":180,"fill":66,"textAnchor":74},"258","text-anchor:middle;font-size:10px;font-family:sans-serif","layers: north · central · south · …",[68,183,187],{"x":70,"y":184,"style":185,"fill":186,"textAnchor":74},"308","text-anchor:middle;font-size:11px;font-family:sans-serif","#59645f","a value with a slash in it breaks the first; the second does not care",[189,190,192],"h2",{"id":191},"prerequisites","Prerequisites",[194,195,196,204,207],"ul",{},[197,198,199,203],"li",{},[200,201,202],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[197,205,206],{},"A layer with a field of manageable cardinality. Splitting on a field with fifty thousand distinct values produces fifty thousand files and a very unhappy filesystem.",[197,208,209],{},"An empty output directory. The algorithm does not clear one, so a re-run leaves stale files from the previous split alongside the new ones.",[189,211,213],{"id":212},"the-algorithm","The algorithm",[14,215,216],{},"One call, and the outputs land in a directory.",[218,219,224],"pre",{"className":220,"code":221,"language":222,"meta":223,"style":223},"language-python shiki shiki-themes github-dark","import processing\n\nprocessing.run(\"native:splitvectorlayer\", {\n    \"INPUT\": \"\u002Fdata\u002Fparcels.gpkg\",\n    \"FIELD\": \"district\",\n    \"PREFIX_FIELD\": True,\n    \"FILE_TYPE\": 0,                     # 0 GeoPackage, 1 GML, 2 GeoJSON, 3 SHP…\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fby_district\u002F\",\n})\n","python","",[225,226,227,239,246,259,274,287,301,318,331],"code",{"__ignoreMap":223},[228,229,231,235],"span",{"class":107,"line":230},1,[228,232,234],{"class":233},"snl16","import",[228,236,238],{"class":237},"s95oV"," processing\n",[228,240,242],{"class":107,"line":241},2,[228,243,245],{"emptyLinePlaceholder":244},true,"\n",[228,247,249,252,256],{"class":107,"line":248},3,[228,250,251],{"class":237},"processing.run(",[228,253,255],{"class":254},"sU2Wk","\"native:splitvectorlayer\"",[228,257,258],{"class":237},", {\n",[228,260,262,265,268,271],{"class":107,"line":261},4,[228,263,264],{"class":254},"    \"INPUT\"",[228,266,267],{"class":237},": ",[228,269,270],{"class":254},"\"\u002Fdata\u002Fparcels.gpkg\"",[228,272,273],{"class":237},",\n",[228,275,277,280,282,285],{"class":107,"line":276},5,[228,278,279],{"class":254},"    \"FIELD\"",[228,281,267],{"class":237},[228,283,284],{"class":254},"\"district\"",[228,286,273],{"class":237},[228,288,290,293,295,299],{"class":107,"line":289},6,[228,291,292],{"class":254},"    \"PREFIX_FIELD\"",[228,294,267],{"class":237},[228,296,298],{"class":297},"sDLfK","True",[228,300,273],{"class":237},[228,302,304,307,309,311,314],{"class":107,"line":303},7,[228,305,306],{"class":254},"    \"FILE_TYPE\"",[228,308,267],{"class":237},[228,310,46],{"class":297},[228,312,313],{"class":237},",                     ",[228,315,317],{"class":316},"sjoCn","# 0 GeoPackage, 1 GML, 2 GeoJSON, 3 SHP…\n",[228,319,321,324,326,329],{"class":107,"line":320},8,[228,322,323],{"class":254},"    \"OUTPUT\"",[228,325,267],{"class":237},[228,327,328],{"class":254},"\"\u002Fdata\u002Foutput\u002Fby_district\u002F\"",[228,330,273],{"class":237},[228,332,334],{"class":107,"line":333},9,[228,335,336],{"class":237},"})\n",[14,338,339,342,343,346,347,350,351,354,355,357,358,361],{},[200,340,341],{},"Breakdown:"," ",[225,344,345],{},"PREFIX_FIELD: True"," prefixes each filename with the field name, giving ",[225,348,349],{},"district_north.gpkg","; with ",[225,352,353],{},"False"," you get ",[225,356,143],{},". ",[225,359,360],{},"FILE_TYPE"," selects the driver by index, which is brittle to read — GeoPackage is the sensible default because it has no field-name length limit and no encoding ambiguity. The output must be a directory path, and the algorithm creates it if missing.",[14,363,364,365,368],{},"What the algorithm will not do is control the filename beyond that prefix. A district called ",[225,366,367],{},"North \u002F East"," produces a filename containing a slash, which fails on every platform, and a value with a leading space produces a file nobody can type. Where the field values are not already clean identifiers, write the loop yourself.",[189,370,372],{"id":371},"a-hand-written-split-with-predictable-names","A hand-written split with predictable names",[14,374,375],{},"Ten lines, and every filename is one you chose.",[218,377,379],{"className":220,"code":378,"language":222,"meta":223,"style":223},"import re\nfrom pathlib import Path\nfrom qgis.core import (\n    QgsProject, QgsVectorLayer, QgsVectorFileWriter,\n    QgsCoordinateTransformContext,\n)\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nfield = \"district\"\nout_dir = Path(\"\u002Fdata\u002Foutput\u002Fby_district\")\nout_dir.mkdir(parents=True, exist_ok=True)\n\nvalues = sorted({f[field] for f in layer.getFeatures() if f[field] is not None})\n\nfor value in values:\n    slug = re.sub(r\"[^a-z0-9]+\", \"_\", str(value).lower()).strip(\"_\") or \"unnamed\"\n    layer.selectByExpression(f'\"{field}\" = \\'{value}\\'')\n\n    options = QgsVectorFileWriter.SaveVectorOptions()\n    options.driverName = \"GPKG\"\n    options.onlySelectedFeatures = True\n    options.fileEncoding = \"UTF-8\"\n\n    QgsVectorFileWriter.writeAsVectorFormatV3(\n        layer, str(out_dir \u002F f\"{slug}.gpkg\"),\n        QgsCoordinateTransformContext(), options,\n    )\n\nlayer.removeSelection()\n",[225,380,381,388,401,413,418,423,428,432,454,464,480,506,511,554,559,572,627,665,670,681,692,703,714,719,725,757,763,769,774],{"__ignoreMap":223},[228,382,383,385],{"class":107,"line":230},[228,384,234],{"class":233},[228,386,387],{"class":237}," re\n",[228,389,390,393,396,398],{"class":107,"line":241},[228,391,392],{"class":233},"from",[228,394,395],{"class":237}," pathlib ",[228,397,234],{"class":233},[228,399,400],{"class":237}," Path\n",[228,402,403,405,408,410],{"class":107,"line":248},[228,404,392],{"class":233},[228,406,407],{"class":237}," qgis.core ",[228,409,234],{"class":233},[228,411,412],{"class":237}," (\n",[228,414,415],{"class":107,"line":261},[228,416,417],{"class":237},"    QgsProject, QgsVectorLayer, QgsVectorFileWriter,\n",[228,419,420],{"class":107,"line":276},[228,421,422],{"class":237},"    QgsCoordinateTransformContext,\n",[228,424,425],{"class":107,"line":289},[228,426,427],{"class":237},")\n",[228,429,430],{"class":107,"line":303},[228,431,245],{"emptyLinePlaceholder":244},[228,433,434,437,440,443,446,449,451],{"class":107,"line":320},[228,435,436],{"class":237},"layer ",[228,438,439],{"class":233},"=",[228,441,442],{"class":237}," QgsProject.instance().mapLayersByName(",[228,444,445],{"class":254},"\"parcels\"",[228,447,448],{"class":237},")[",[228,450,46],{"class":297},[228,452,453],{"class":237},"]\n",[228,455,456,459,461],{"class":107,"line":333},[228,457,458],{"class":237},"field ",[228,460,439],{"class":233},[228,462,463],{"class":254}," \"district\"\n",[228,465,467,470,472,475,478],{"class":107,"line":466},10,[228,468,469],{"class":237},"out_dir ",[228,471,439],{"class":233},[228,473,474],{"class":237}," Path(",[228,476,477],{"class":254},"\"\u002Fdata\u002Foutput\u002Fby_district\"",[228,479,427],{"class":237},[228,481,483,486,490,492,494,497,500,502,504],{"class":107,"line":482},11,[228,484,485],{"class":237},"out_dir.mkdir(",[228,487,489],{"class":488},"s9osk","parents",[228,491,439],{"class":233},[228,493,298],{"class":297},[228,495,496],{"class":237},", ",[228,498,499],{"class":488},"exist_ok",[228,501,439],{"class":233},[228,503,298],{"class":297},[228,505,427],{"class":237},[228,507,509],{"class":107,"line":508},12,[228,510,245],{"emptyLinePlaceholder":244},[228,512,514,517,519,522,525,528,531,534,537,540,543,546,549,552],{"class":107,"line":513},13,[228,515,516],{"class":237},"values ",[228,518,439],{"class":233},[228,520,521],{"class":297}," sorted",[228,523,524],{"class":237},"({f[field] ",[228,526,527],{"class":233},"for",[228,529,530],{"class":237}," f ",[228,532,533],{"class":233},"in",[228,535,536],{"class":237}," layer.getFeatures() ",[228,538,539],{"class":233},"if",[228,541,542],{"class":237}," f[field] ",[228,544,545],{"class":233},"is",[228,547,548],{"class":233}," not",[228,550,551],{"class":297}," None",[228,553,336],{"class":237},[228,555,557],{"class":107,"line":556},14,[228,558,245],{"emptyLinePlaceholder":244},[228,560,562,564,567,569],{"class":107,"line":561},15,[228,563,527],{"class":233},[228,565,566],{"class":237}," value ",[228,568,533],{"class":233},[228,570,571],{"class":237}," values:\n",[228,573,575,578,580,583,586,589,592,595,598,601,603,605,608,610,613,616,618,621,624],{"class":107,"line":574},16,[228,576,577],{"class":237},"    slug ",[228,579,439],{"class":233},[228,581,582],{"class":237}," re.sub(",[228,584,585],{"class":233},"r",[228,587,588],{"class":254},"\"",[228,590,591],{"class":297},"[",[228,593,594],{"class":233},"^",[228,596,597],{"class":297},"a-z0-9]",[228,599,600],{"class":233},"+",[228,602,588],{"class":254},[228,604,496],{"class":237},[228,606,607],{"class":254},"\"_\"",[228,609,496],{"class":237},[228,611,612],{"class":297},"str",[228,614,615],{"class":237},"(value).lower()).strip(",[228,617,607],{"class":254},[228,619,620],{"class":237},") ",[228,622,623],{"class":233},"or",[228,625,626],{"class":254}," \"unnamed\"\n",[228,628,630,633,636,639,642,645,648,651,654,657,660,663],{"class":107,"line":629},17,[228,631,632],{"class":237},"    layer.selectByExpression(",[228,634,635],{"class":233},"f",[228,637,638],{"class":254},"'\"",[228,640,641],{"class":297},"{",[228,643,644],{"class":237},"field",[228,646,647],{"class":297},"}",[228,649,650],{"class":254},"\" = ",[228,652,653],{"class":297},"\\'{",[228,655,656],{"class":237},"value",[228,658,659],{"class":297},"}\\'",[228,661,662],{"class":254},"'",[228,664,427],{"class":237},[228,666,668],{"class":107,"line":667},18,[228,669,245],{"emptyLinePlaceholder":244},[228,671,673,676,678],{"class":107,"line":672},19,[228,674,675],{"class":237},"    options ",[228,677,439],{"class":233},[228,679,680],{"class":237}," QgsVectorFileWriter.SaveVectorOptions()\n",[228,682,684,687,689],{"class":107,"line":683},20,[228,685,686],{"class":237},"    options.driverName ",[228,688,439],{"class":233},[228,690,691],{"class":254}," \"GPKG\"\n",[228,693,695,698,700],{"class":107,"line":694},21,[228,696,697],{"class":237},"    options.onlySelectedFeatures ",[228,699,439],{"class":233},[228,701,702],{"class":297}," True\n",[228,704,706,709,711],{"class":107,"line":705},22,[228,707,708],{"class":237},"    options.fileEncoding ",[228,710,439],{"class":233},[228,712,713],{"class":254}," \"UTF-8\"\n",[228,715,717],{"class":107,"line":716},23,[228,718,245],{"emptyLinePlaceholder":244},[228,720,722],{"class":107,"line":721},24,[228,723,724],{"class":237},"    QgsVectorFileWriter.writeAsVectorFormatV3(\n",[228,726,728,731,733,736,739,742,744,746,749,751,754],{"class":107,"line":727},25,[228,729,730],{"class":237},"        layer, ",[228,732,612],{"class":297},[228,734,735],{"class":237},"(out_dir ",[228,737,738],{"class":233},"\u002F",[228,740,741],{"class":233}," f",[228,743,588],{"class":254},[228,745,641],{"class":297},[228,747,748],{"class":237},"slug",[228,750,647],{"class":297},[228,752,753],{"class":254},".gpkg\"",[228,755,756],{"class":237},"),\n",[228,758,760],{"class":107,"line":759},26,[228,761,762],{"class":237},"        QgsCoordinateTransformContext(), options,\n",[228,764,766],{"class":107,"line":765},27,[228,767,768],{"class":237},"    )\n",[228,770,772],{"class":107,"line":771},28,[228,773,245],{"emptyLinePlaceholder":244},[228,775,777],{"class":107,"line":776},29,[228,778,779],{"class":237},"layer.removeSelection()\n",[14,781,782,784,785,788,789,792,793,796,797,800,801,804,805,808,809,812],{},[200,783,341],{}," Building the slug with a regular expression handles slashes, spaces, accents and empty strings in one line, and the ",[225,786,787],{},"or \"unnamed\""," fallback covers a value that reduces to nothing. ",[225,790,791],{},"selectByExpression"," plus ",[225,794,795],{},"onlySelectedFeatures"," is the simplest filter that works for any provider; for a large layer, setting a subset string or using a ",[225,798,799],{},"QgsFeatureRequest"," avoids materialising a selection. ",[225,802,803],{},"writeAsVectorFormatV3"," is the current writer — ",[225,806,807],{},"writeAsVectorFormat"," and ",[225,810,811],{},"V2"," are deprecated and produce warnings that clutter a log. Clearing the selection at the end is a courtesy that avoids leaving the user's map in a strange state.",[14,814,815,816,819],{},"The string interpolation into the expression is a real hazard: a value containing an apostrophe breaks it. For untrusted values, build the expression with ",[225,817,818],{},"QgsExpression.quotedValue(value)"," instead of an f-string.",[189,821,823],{"id":822},"everything-in-one-geopackage","Everything in one GeoPackage",[14,825,826],{},"Where the set belongs together, many layers in one file is tidier than many files, and immune to filename problems.",[218,828,830],{"className":220,"code":829,"language":222,"meta":223,"style":223},"first = True\nfor value in values:\n    layer.selectByExpression(\n        f'\"{field}\" = {QgsExpression.quotedValue(value)}'\n    )\n    options = QgsVectorFileWriter.SaveVectorOptions()\n    options.driverName = \"GPKG\"\n    options.layerName = str(value)[:60]\n    options.onlySelectedFeatures = True\n    options.actionOnExistingFile = (\n        QgsVectorFileWriter.CreateOrOverwriteFile if first\n        else QgsVectorFileWriter.CreateOrOverwriteLayer\n    )\n    QgsVectorFileWriter.writeAsVectorFormatV3(\n        layer, \"\u002Fdata\u002Foutput\u002Fdistricts.gpkg\",\n        QgsCoordinateTransformContext(), options,\n    )\n    first = False\n",[225,831,832,841,851,856,880,884,892,900,918,926,935,945,953,957,961,970,974,978],{"__ignoreMap":223},[228,833,834,837,839],{"class":107,"line":230},[228,835,836],{"class":237},"first ",[228,838,439],{"class":233},[228,840,702],{"class":297},[228,842,843,845,847,849],{"class":107,"line":241},[228,844,527],{"class":233},[228,846,566],{"class":237},[228,848,533],{"class":233},[228,850,571],{"class":237},[228,852,853],{"class":107,"line":248},[228,854,855],{"class":237},"    layer.selectByExpression(\n",[228,857,858,861,863,865,867,869,871,873,875,877],{"class":107,"line":261},[228,859,860],{"class":233},"        f",[228,862,638],{"class":254},[228,864,641],{"class":297},[228,866,644],{"class":237},[228,868,647],{"class":297},[228,870,650],{"class":254},[228,872,641],{"class":297},[228,874,818],{"class":237},[228,876,647],{"class":297},[228,878,879],{"class":254},"'\n",[228,881,882],{"class":107,"line":276},[228,883,768],{"class":237},[228,885,886,888,890],{"class":107,"line":289},[228,887,675],{"class":237},[228,889,439],{"class":233},[228,891,680],{"class":237},[228,893,894,896,898],{"class":107,"line":303},[228,895,686],{"class":237},[228,897,439],{"class":233},[228,899,691],{"class":254},[228,901,902,905,907,910,913,916],{"class":107,"line":320},[228,903,904],{"class":237},"    options.layerName ",[228,906,439],{"class":233},[228,908,909],{"class":297}," str",[228,911,912],{"class":237},"(value)[:",[228,914,915],{"class":297},"60",[228,917,453],{"class":237},[228,919,920,922,924],{"class":107,"line":333},[228,921,697],{"class":237},[228,923,439],{"class":233},[228,925,702],{"class":297},[228,927,928,931,933],{"class":107,"line":466},[228,929,930],{"class":237},"    options.actionOnExistingFile ",[228,932,439],{"class":233},[228,934,412],{"class":237},[228,936,937,940,942],{"class":107,"line":482},[228,938,939],{"class":237},"        QgsVectorFileWriter.CreateOrOverwriteFile ",[228,941,539],{"class":233},[228,943,944],{"class":237}," first\n",[228,946,947,950],{"class":107,"line":508},[228,948,949],{"class":233},"        else",[228,951,952],{"class":237}," QgsVectorFileWriter.CreateOrOverwriteLayer\n",[228,954,955],{"class":107,"line":513},[228,956,768],{"class":237},[228,958,959],{"class":107,"line":556},[228,960,724],{"class":237},[228,962,963,965,968],{"class":107,"line":561},[228,964,730],{"class":237},[228,966,967],{"class":254},"\"\u002Fdata\u002Foutput\u002Fdistricts.gpkg\"",[228,969,273],{"class":237},[228,971,972],{"class":107,"line":574},[228,973,762],{"class":237},[228,975,976],{"class":107,"line":629},[228,977,768],{"class":237},[228,979,980,983,985],{"class":107,"line":667},[228,981,982],{"class":237},"    first ",[228,984,439],{"class":233},[228,986,987],{"class":297}," False\n",[14,989,990,342,992,995,996,999,1000,1003,1004,1007],{},[200,991,341],{},[225,993,994],{},"actionOnExistingFile"," is the whole trick. The first write creates the file with ",[225,997,998],{},"CreateOrOverwriteFile","; every later write must use ",[225,1001,1002],{},"CreateOrOverwriteLayer",", which adds a layer to the existing container. Getting this backwards means each write replaces the file and you end up with only the last district. ",[225,1005,1006],{},"layerName"," accepts spaces and most punctuation because it is a table name inside SQLite rather than a path, which is why this route sidesteps the slug problem entirely.",[14,1009,1010],{},[29,1011,1014,1017,1020,1023,1030,1033,1037,1042,1047,1053,1058,1061,1065,1068,1072,1076,1079,1085,1089,1093,1097,1099,1102,1105,1107,1109,1112,1114,1117],{"viewBox":1012,"role":32,"ariaLabel":1013,"xmlns":34},"0 0 760 280","The sequence of write actions for building one GeoPackage with many layers: the first write creates the file, and every subsequent write adds a layer rather than replacing the file",[36,1015,1016],{},"Create the file once, add layers thereafter",[40,1018,1019],{},"The first call uses CreateOrOverwriteFile and produces a GeoPackage containing one layer. Each subsequent call uses CreateOrOverwriteLayer and appends another layer to the same file. Using CreateOrOverwriteFile every time replaces the file each round, leaving only the final layer.",[44,1021],{"x":46,"y":46,"width":47,"height":1022,"fill":49},"280",[51,1024,1025],{},[54,1026,1028],{"id":1027,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"gpkArrow",[63,1029],{"d":65,"fill":66},[68,1031,1032],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"One flag decides whether you keep twelve layers or one",[44,1034],{"x":1035,"y":120,"width":1036,"height":79,"rx":82,"fill":123,"stroke":124,"style":85},"24","712",[68,1038,1041],{"x":1039,"y":128,"style":1040,"fill":124,"textAnchor":74},"120","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","correct",[44,1043],{"x":1044,"y":79,"width":1045,"height":1046,"rx":59,"fill":136,"stroke":124,"style":137},"200","150","40",[68,1048,1052],{"x":1049,"y":1050,"style":1051,"fill":66,"textAnchor":74},"275","112","text-anchor:middle;font-size:9.5px;font-family:monospace","OverwriteFile",[68,1054,1057],{"x":1049,"y":1055,"style":1056,"fill":66,"textAnchor":74},"128","text-anchor:middle;font-size:9.5px;font-family:sans-serif","creates, 1 layer",[44,1059],{"x":1060,"y":79,"width":1045,"height":1046,"rx":59,"fill":136,"stroke":124,"style":137},"374",[68,1062,1064],{"x":1063,"y":1050,"style":1051,"fill":66,"textAnchor":74},"449","OverwriteLayer",[68,1066,1067],{"x":1063,"y":1055,"style":1056,"fill":66,"textAnchor":74},"adds, 2 layers",[44,1069],{"x":1070,"y":79,"width":1071,"height":1046,"rx":59,"fill":136,"stroke":124,"style":137},"548","164",[68,1073,1075],{"x":1074,"y":1050,"style":1051,"fill":66,"textAnchor":74},"630","OverwriteLayer ×10",[68,1077,1078],{"x":1074,"y":1055,"style":1056,"fill":66,"textAnchor":74},"12 layers",[107,1080],{"x1":1081,"y1":1082,"x2":1083,"y2":1082,"stroke":66,"style":1084},"354","114","370","stroke-width:1.8;marker-end:url(#gpkArrow)",[107,1086],{"x1":1087,"y1":1082,"x2":1088,"y2":1082,"stroke":66,"style":1084},"528","544",[44,1090],{"x":1035,"y":1091,"width":1036,"height":79,"rx":82,"fill":161,"stroke":1092,"style":85},"166","#b91c1c",[68,1094,1096],{"x":1039,"y":1095,"style":1040,"fill":1092,"textAnchor":74},"192","wrong",[44,1098],{"x":1044,"y":109,"width":1045,"height":1046,"rx":59,"fill":171,"stroke":1092,"style":137},[68,1100,1052],{"x":1049,"y":1101,"style":1051,"fill":66,"textAnchor":74},"222",[68,1103,1057],{"x":1049,"y":1104,"style":1056,"fill":66,"textAnchor":74},"238",[44,1106],{"x":1060,"y":109,"width":1045,"height":1046,"rx":59,"fill":171,"stroke":1092,"style":137},[68,1108,1052],{"x":1063,"y":1101,"style":1051,"fill":66,"textAnchor":74},[68,1110,1111],{"x":1063,"y":1104,"style":1056,"fill":66,"textAnchor":74},"replaces, 1 layer",[44,1113],{"x":1070,"y":109,"width":1071,"height":1046,"rx":59,"fill":171,"stroke":1092,"style":137},[68,1115,1116],{"x":1074,"y":1101,"style":1051,"fill":66,"textAnchor":74},"OverwriteFile ×10",[68,1118,1119],{"x":1074,"y":1104,"style":1056,"fill":66,"textAnchor":74},"1 layer, the last one",[189,1121,1123],{"id":1122},"verifying-the-split","Verifying the split",[14,1125,1126],{},"A split is one of the few operations where a complete correctness check is cheap, so there is no excuse for skipping it. Every input feature should appear in exactly one output, and the geometries should be untouched.",[218,1128,1130],{"className":220,"code":1129,"language":222,"meta":223,"style":223},"from pathlib import Path\nfrom qgis.core import QgsVectorLayer\n\ntotal = layer.featureCount()\nwritten = 0\nfor path in sorted(Path(\"\u002Fdata\u002Foutput\u002Fby_district\").glob(\"*.gpkg\")):\n    part = QgsVectorLayer(str(path), path.stem, \"ogr\")\n    if not part.isValid():\n        raise RuntimeError(f\"{path.name} did not open\")\n    written += part.featureCount()\n\nif written != total:\n    raise RuntimeError(f\"split lost features: {total} in, {written} out\")\nprint(f\"{total} features across {len(values)} outputs — accounted for\")\n",[225,1131,1132,1142,1153,1157,1167,1177,1202,1222,1232,1259,1270,1274,1287,1323],{"__ignoreMap":223},[228,1133,1134,1136,1138,1140],{"class":107,"line":230},[228,1135,392],{"class":233},[228,1137,395],{"class":237},[228,1139,234],{"class":233},[228,1141,400],{"class":237},[228,1143,1144,1146,1148,1150],{"class":107,"line":241},[228,1145,392],{"class":233},[228,1147,407],{"class":237},[228,1149,234],{"class":233},[228,1151,1152],{"class":237}," QgsVectorLayer\n",[228,1154,1155],{"class":107,"line":248},[228,1156,245],{"emptyLinePlaceholder":244},[228,1158,1159,1162,1164],{"class":107,"line":261},[228,1160,1161],{"class":237},"total ",[228,1163,439],{"class":233},[228,1165,1166],{"class":237}," layer.featureCount()\n",[228,1168,1169,1172,1174],{"class":107,"line":276},[228,1170,1171],{"class":237},"written ",[228,1173,439],{"class":233},[228,1175,1176],{"class":297}," 0\n",[228,1178,1179,1181,1184,1186,1188,1191,1193,1196,1199],{"class":107,"line":289},[228,1180,527],{"class":233},[228,1182,1183],{"class":237}," path ",[228,1185,533],{"class":233},[228,1187,521],{"class":297},[228,1189,1190],{"class":237},"(Path(",[228,1192,477],{"class":254},[228,1194,1195],{"class":237},").glob(",[228,1197,1198],{"class":254},"\"*.gpkg\"",[228,1200,1201],{"class":237},")):\n",[228,1203,1204,1207,1209,1212,1214,1217,1220],{"class":107,"line":303},[228,1205,1206],{"class":237},"    part ",[228,1208,439],{"class":233},[228,1210,1211],{"class":237}," QgsVectorLayer(",[228,1213,612],{"class":297},[228,1215,1216],{"class":237},"(path), path.stem, ",[228,1218,1219],{"class":254},"\"ogr\"",[228,1221,427],{"class":237},[228,1223,1224,1227,1229],{"class":107,"line":320},[228,1225,1226],{"class":233},"    if",[228,1228,548],{"class":233},[228,1230,1231],{"class":237}," part.isValid():\n",[228,1233,1234,1237,1240,1243,1245,1247,1249,1252,1254,1257],{"class":107,"line":333},[228,1235,1236],{"class":233},"        raise",[228,1238,1239],{"class":297}," RuntimeError",[228,1241,1242],{"class":237},"(",[228,1244,635],{"class":233},[228,1246,588],{"class":254},[228,1248,641],{"class":297},[228,1250,1251],{"class":237},"path.name",[228,1253,647],{"class":297},[228,1255,1256],{"class":254}," did not open\"",[228,1258,427],{"class":237},[228,1260,1261,1264,1267],{"class":107,"line":466},[228,1262,1263],{"class":237},"    written ",[228,1265,1266],{"class":233},"+=",[228,1268,1269],{"class":237}," part.featureCount()\n",[228,1271,1272],{"class":107,"line":482},[228,1273,245],{"emptyLinePlaceholder":244},[228,1275,1276,1278,1281,1284],{"class":107,"line":508},[228,1277,539],{"class":233},[228,1279,1280],{"class":237}," written ",[228,1282,1283],{"class":233},"!=",[228,1285,1286],{"class":237}," total:\n",[228,1288,1289,1292,1294,1296,1298,1301,1303,1306,1308,1311,1313,1316,1318,1321],{"class":107,"line":513},[228,1290,1291],{"class":233},"    raise",[228,1293,1239],{"class":297},[228,1295,1242],{"class":237},[228,1297,635],{"class":233},[228,1299,1300],{"class":254},"\"split lost features: ",[228,1302,641],{"class":297},[228,1304,1305],{"class":237},"total",[228,1307,647],{"class":297},[228,1309,1310],{"class":254}," in, ",[228,1312,641],{"class":297},[228,1314,1315],{"class":237},"written",[228,1317,647],{"class":297},[228,1319,1320],{"class":254}," out\"",[228,1322,427],{"class":237},[228,1324,1325,1328,1330,1332,1334,1336,1338,1340,1343,1346,1349,1351,1354],{"class":107,"line":556},[228,1326,1327],{"class":297},"print",[228,1329,1242],{"class":237},[228,1331,635],{"class":233},[228,1333,588],{"class":254},[228,1335,641],{"class":297},[228,1337,1305],{"class":237},[228,1339,647],{"class":297},[228,1341,1342],{"class":254}," features across ",[228,1344,1345],{"class":297},"{len",[228,1347,1348],{"class":237},"(values)",[228,1350,647],{"class":297},[228,1352,1353],{"class":254}," outputs — accounted for\"",[228,1355,427],{"class":237},[14,1357,1358,1360],{},[200,1359,341],{}," Opening every output also confirms each file is readable, which catches a truncated write from a full disk that the writer reported as success. Comparing counts catches nulls dropped silently, features filtered out by a mistyped expression, and the duplicate-write case where a feature landed in two outputs. Raising rather than printing is right here: a partial split that looks complete is worse than no split, because the missing rows are discovered by whoever receives the data.",[14,1362,1363],{},"For a stricter check, compare the sum of feature ids rather than the count — two errors that cancel out in a count will not cancel in a sum. That is rarely necessary, but it costs one more expression and is worth it when the outputs are going somewhere you cannot easily correct.",[189,1365,1367],{"id":1366},"choosing-between-the-two-shapes","Choosing between the two shapes",[14,1369,1370],{},"Directory-of-files wins when each group goes to a different person or system: a folder per district handed to a district office, one shapefile per operator uploaded to separate portals. The files are independent, individually zippable, and need no explanation.",[14,1372,1373,1374,1378],{},"One GeoPackage wins for everything else. It keeps the set together so nothing gets separated in transit, stores one copy of the spatial reference definition, holds styles in its own table, and accepts layer names that would be illegal as filenames. It is also dramatically kinder to a filesystem when the split produces hundreds of groups — a directory of eight hundred shapefiles is four thousand files, and the ",[21,1375,1377],{"href":1376},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fmerge-multiple-shapefiles-pyqgis\u002F","merge back"," is correspondingly slower.",[189,1380,1382],{"id":1381},"nulls-blanks-and-the-group-you-forgot","Nulls, blanks and the group you forgot",[14,1384,1385],{},"A field with nulls produces a group the split usually drops silently, and those features then exist in no output at all.",[218,1387,1389],{"className":220,"code":1388,"language":222,"meta":223,"style":223},"missing = layer.materialize(\n    QgsFeatureRequest().setFilterExpression(f'\"{field}\" IS NULL OR trim(\"{field}\") = \\'\\'')\n)\nprint(f\"{missing.featureCount()} feature(s) have no {field} value\")\n",[225,1390,1391,1401,1435,1439],{"__ignoreMap":223},[228,1392,1393,1396,1398],{"class":107,"line":230},[228,1394,1395],{"class":237},"missing ",[228,1397,439],{"class":233},[228,1399,1400],{"class":237}," layer.materialize(\n",[228,1402,1403,1406,1408,1410,1412,1414,1416,1419,1421,1423,1425,1428,1431,1433],{"class":107,"line":241},[228,1404,1405],{"class":237},"    QgsFeatureRequest().setFilterExpression(",[228,1407,635],{"class":233},[228,1409,638],{"class":254},[228,1411,641],{"class":297},[228,1413,644],{"class":237},[228,1415,647],{"class":297},[228,1417,1418],{"class":254},"\" IS NULL OR trim(\"",[228,1420,641],{"class":297},[228,1422,644],{"class":237},[228,1424,647],{"class":297},[228,1426,1427],{"class":254},"\") = ",[228,1429,1430],{"class":297},"\\'\\'",[228,1432,662],{"class":254},[228,1434,427],{"class":237},[228,1436,1437],{"class":107,"line":248},[228,1438,427],{"class":237},[228,1440,1441,1443,1445,1447,1449,1451,1454,1456,1459,1461,1463,1465,1468],{"class":107,"line":261},[228,1442,1327],{"class":297},[228,1444,1242],{"class":237},[228,1446,635],{"class":233},[228,1448,588],{"class":254},[228,1450,641],{"class":297},[228,1452,1453],{"class":237},"missing.featureCount()",[228,1455,647],{"class":297},[228,1457,1458],{"class":254}," feature(s) have no ",[228,1460,641],{"class":297},[228,1462,644],{"class":237},[228,1464,647],{"class":297},[228,1466,1467],{"class":254}," value\"",[228,1469,427],{"class":237},[14,1471,1472,1474,1475,1478,1479,1482],{},[200,1473,341],{}," Counting before splitting turns a silent loss into a decision: write them to an ",[225,1476,1477],{},"unassigned"," output, fix them upstream, or accept the loss knowingly. ",[225,1480,1481],{},"materialize()"," builds an in-memory layer from a request without writing anything, which is the cheapest way to look at a subset. Testing for a trimmed empty string as well as null catches the very common case of a field that was populated with spaces rather than left null.",[14,1484,1485],{},"A related check is the total: the sum of the output feature counts should equal the input count. Writing that assertion into the script costs one line and catches every category of silent loss at once.",[189,1487,1489],{"id":1488},"qgis-version-compatibility","QGIS version compatibility",[14,1491,1492,1493,1495],{},"The examples target ",[200,1494,202],{}," (Python 3.12).",[1497,1498,1499,1515],"table",{},[1500,1501,1502],"thead",{},[1503,1504,1505,1509,1512],"tr",{},[1506,1507,1508],"th",{},"QGIS version",[1506,1510,1511],{},"Python",[1506,1513,1514],{},"Notes",[1516,1517,1518,1537,1550,1563,1574],"tbody",{},[1503,1519,1520,1524,1527],{},[1521,1522,1523],"td",{},"3.16 LTR",[1521,1525,1526],{},"3.7",[1521,1528,1529,1532,1533,1536],{},[225,1530,1531],{},"native:splitvectorlayer"," present; ",[225,1534,1535],{},"writeAsVectorFormatV2"," is current.",[1503,1538,1539,1542,1545],{},[1521,1540,1541],{},"3.20",[1521,1543,1544],{},"3.9",[1521,1546,1547,1549],{},[225,1548,803],{}," introduced; V2 deprecated.",[1503,1551,1552,1555,1557],{},[1521,1553,1554],{},"3.28 LTR",[1521,1556,1544],{},[1521,1558,1559,1562],{},[225,1560,1561],{},"PREFIX_FIELD"," parameter added to the split algorithm.",[1503,1564,1565,1568,1571],{},[1521,1566,1567],{},"3.34 LTR",[1521,1569,1570],{},"3.12",[1521,1572,1573],{},"Baseline for this page.",[1503,1575,1576,1579,1581],{},[1521,1577,1578],{},"3.40+",[1521,1580,1570],{},[1521,1582,1583],{},"Split algorithm gains an option to skip empty groups explicitly.",[189,1585,1587],{"id":1586},"troubleshooting","Troubleshooting",[194,1589,1590,1601,1607,1613,1623,1629],{},[197,1591,1592,342,1595,1597,1598,1600],{},[200,1593,1594],{},"Only one output file exists.",[225,1596,998],{}," was used on every iteration. Switch to ",[225,1599,1002],{}," after the first.",[197,1602,1603,1606],{},[200,1604,1605],{},"A filename contains a slash."," The field value did. Slugify, or split into one GeoPackage instead.",[197,1608,1609,1612],{},[200,1610,1611],{},"Some features are in no output."," Their field value is null or blank. Count them explicitly and decide.",[197,1614,1615,1618,1619,1622],{},[200,1616,1617],{},"The expression fails on some values."," A value contains an apostrophe. Use ",[225,1620,1621],{},"QgsExpression.quotedValue()",".",[197,1624,1625,1628],{},[200,1626,1627],{},"Old files remain from a previous run."," The algorithm does not clear the directory. Empty it first.",[197,1630,1631,1634],{},[200,1632,1633],{},"Field names are truncated in the outputs."," The driver is Shapefile with its ten-character limit. Use GeoPackage.",[189,1636,1638],{"id":1637},"conclusion","Conclusion",[14,1640,1641,1642,1644],{},"Use ",[225,1643,1531],{}," when the field values are already clean identifiers and a directory of files is the deliverable. Write the loop yourself when filenames matter, quote values properly, count the nulls before you lose them, and prefer one GeoPackage with many layers whenever the set belongs together.",[189,1646,1648],{"id":1647},"frequently-asked-questions","Frequently Asked Questions",[14,1650,1651,1654],{},[200,1652,1653],{},"How do I split by two fields?","\nCreate a virtual or real field concatenating them, then split on that. Building the combined key explicitly also gives you control over the separator, which matters for the filenames.",[14,1656,1657,1660,1661,1664,1665,1669],{},[200,1658,1659],{},"Can I split a layer into equal-sized chunks instead?","\nNot with this algorithm. Add a field computed as ",[225,1662,1663],{},"floor((@row_number - 1) \u002F 5000)"," with the ",[21,1666,1668],{"href":1667},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fupdate-attribute-values-pyqgis\u002F","field calculator"," and split on that.",[14,1671,1672,1675,1676,1680],{},[200,1673,1674],{},"Will the split preserve styling?","\nNo — each output is a fresh layer with default symbology. Apply a ",[21,1677,1679],{"href":1678},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-and-load-qml-style-pyqgis\u002F","QML style"," to each after writing, or save the style into the GeoPackage's style table.",[14,1682,1683,1686,1687,1691,1692,1696],{},[200,1684,1685],{},"Is splitting the right way to speed up a big layer?","\nUsually not. A ",[21,1688,1690],{"href":1689},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Fbuild-spatial-index-pyqgis\u002F","spatial index"," and a subset string give most of the benefit without fragmenting the data. Split when the ",[1693,1694,1695],"em",{},"deliverable"," is per-group, not to make queries faster.",[189,1698,1700],{"id":1699},"related","Related",[194,1702,1703,1708,1713,1719,1725],{},[197,1704,1705,1707],{},[21,1706,24],{"href":23}," — the guide this recipe belongs to",[197,1709,1710],{},[21,1711,1712],{"href":1376},"Merge Multiple Shapefiles in PyQGIS",[197,1714,1715],{},[21,1716,1718],{"href":1717},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fwrite-vector-layer-to-geopackage-pyqgis\u002F","Write a Vector Layer to a GeoPackage in PyQGIS",[197,1720,1721],{},[21,1722,1724],{"href":1723},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-algorithm-over-folder-of-files-pyqgis\u002F","Run an Algorithm Over a Folder of Files in PyQGIS",[197,1726,1727],{},[21,1728,1730],{"href":1729},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fselect-features-by-expression-pyqgis\u002F","Select Features by Expression in PyQGIS",[1732,1733,1734],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}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}",{"title":223,"searchDepth":241,"depth":241,"links":1736},[1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748],{"id":191,"depth":241,"text":192},{"id":212,"depth":241,"text":213},{"id":371,"depth":241,"text":372},{"id":822,"depth":241,"text":823},{"id":1122,"depth":241,"text":1123},{"id":1366,"depth":241,"text":1367},{"id":1381,"depth":241,"text":1382},{"id":1488,"depth":241,"text":1489},{"id":1586,"depth":241,"text":1587},{"id":1637,"depth":241,"text":1638},{"id":1647,"depth":241,"text":1648},{"id":1699,"depth":241,"text":1700},"Break one layer into many by a field value with native:splitvectorlayer, or write the split yourself for control over filenames, formats and empty groups.","md",{"slug":1752,"type":1753,"breadcrumb":1754,"datePublished":1755,"dateModified":1755},"split-vector-layer-by-attribute-pyqgis","article","Split by Attribute","2026-08-27","\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fsplit-vector-layer-by-attribute-pyqgis",{"title":5,"description":1749},"spatial-data-processing-automation\u002Fvector-data-manipulation\u002Fsplit-vector-layer-by-attribute-pyqgis\u002Findex","omt8k_ut9x1_YNYWCu0gTRcg0JKc_UEXs7mdX36Pqqc",1787823364881]