Wie Tax Service, OpenStreetMap und InterSystems IRIS
könnte Entwicklern helfen, saubere Adressen zu erhalten
Pieter Brueghel der Jüngere, Zahlung der Steuer (The Tax Collector), 1640In meinem
vorherigen Artikel haben wir nur die Oberfläche von Objekten überflogen. Lassen Sie uns unsere Aufklärung fortsetzen. Das heutige Thema ist schwierig. Es sind nicht ganz GROSSE DATEN, aber es sind immer noch die Daten, mit denen man nicht einfach arbeiten kann: Wir sprechen von ziemlich großen Datenmengen. Es passt nicht alles auf einmal in den Arbeitsspeicher, und ein Teil davon passt nicht einmal auf das Laufwerk (nicht wegen Platzmangels, sondern weil viel Müll vorhanden ist). Der Name unseres Faches ist
FIAS DB : die Datenbank des Federal Information Address System - die Adressdatenbanken in Russland. Das Archiv ist 5,5 GB groß. Und es ist eine komprimierte XML-Datei. Nach der Extraktion sind es volle 53 GB (110 GB für die Extraktion reserviert). Und wenn Sie anfangen, es zu analysieren und zu konvertieren, reichen diese 110 GB nicht aus. Es wird auch nicht genug RAM geben.
Das wäre alles in Ordnung, aber Sie könnten weiter graben. Es gibt bereits ein internationales Open-Source-Projekt zum Sammeln und Systematisieren von Adressdaten:
OpenAddresses . Ihre Datenbanken werden noch größer sein. Derzeit gibt es viele leere Stellen auf ihren Karten. Russland zum Beispiel ist fast leer. Die Größe dieses Archivs beträgt 10 GB.
Aber zuerst betrachten wir die Datenbank des ziemlich bekannten Projekts
OpenStreetMaps . Es wurde von Freiwilligen nach dem Vorbild von Wikipedia gebaut. Es ist ziemlich gründlich und mehrsprachig. Und das Projekt wurde gerade
2018 mit dem Preis der Free Software Foundation ausgezeichnet . Derzeit beträgt die Größe des gesamten Archivs als komprimierte XML-Datei 74 GB.
Apropos Adressen: Ich habe einige unerwartete
Neuigkeiten von DuckDuckGo erhalten , der bislang besten sicheren Suchmaschine, die ihren Wechsel zu Apple Maps angekündigt hat. Genauer gesagt zu Apple MapKit JS. Das Interessanteste für unsere Zwecke ist die "verbesserte Adressensuche". Ist Apple besser als alle anderen darin, unsere Daten sorgfältig zu sammeln und zu schützen? Wir müssen aufpassen ...
Hier ist also die Herausforderung. Wie können wir all diese Adressdaten in ein benutzerfreundliches Repository stellen, uns eine einfache API vorstellen (natürlich in Python) und verhindern, dass unsere geliebte Hardware unter dieser massiven Belastung zusammenbricht? Nennen wir dies MicroBigData, kurz mD oder µBD.
Fast jeder Entwickler hat den folgenden Witz gehört. Ein Adressverzeichnis ist ein Verzeichnis von Ortsnamen, was sehr nützlich ist. Ich weiß nicht genau, wie viele Adressdaten in Ihren Projekten aktuell sind. Immerhin gibt es so viele Regionen, Städte und Straßen. Aber es scheint, dass sie für jedes Projekt notwendig sind, an dem Menschen beteiligt sind. Es gibt Adressen, an denen Sie jemanden finden oder ihm ein Paket senden können. Dann gibt es die Informationen, die für Pässe und andere Dokumente benötigt werden. Und vielleicht gibt es eine Adresse eines Büros oder einer Sehenswürdigkeit, die Ihnen jemand empfohlen hat. Was solltest du also tun? Wo solltest du es bekommen?
Ohne Berücksichtigung von Fehlern und Duplikaten umfasst die einfachste Lösung primitive Objekte, die einfache Zeichenfolgenliterale (oder Zeichenfolgenkonstanten) enthalten. Lassen Sie Benutzer zusätzliche Einträge vornehmen. Und Objekte wissen, wie sie sich selbst retten können, wie wir bereits beschrieben haben.
Nehmen Sie als Beispiel die in der folgenden Klasse beschriebenen Objekte.
Ein Lehrbuchfall , allerdings in Form einer USA-Adresse, aber mit einer Anpassung für unseren russischen Datensatz: Postleitzahl statt Postleitzahl. Ich würde auch die Postleitzahl in eine Nummer ändern, aber ich werde sie als Zeichenfolge belassen, um die Dinge einheitlich zu halten. Jeder, der die Sprache (ObjectScript) sofort erkannt hat, erhält ein kostenloses "Gefällt mir".
Class Sample.Address Extends %Persistent { Property streetName As %String; Property cityName As %String; Property areaName As %String; Property postalCode As %String; }
Natürlich werden viele Leute schlecht weinen und sagen, dass alle Literale aus dem Objekt herausragen. Wer hat von einem Objekt gehört, das öffentlich seine Felder lüftet? Lassen wir es jetzt so. Es ist ein ziemlich beredtes Beispiel, und jeder Schüler könnte es verstehen.
Das ist eigentlich alles was wir brauchen. Wir haben die Felder ausgefüllt. Gespeichert sie. Übergab sie an andere Objekte. Jemand anderes wird sie erben. Es funktioniert alles. Und es ist gespeichert!
Aber ich muss ein paar Worte darüber sagen, warum es nicht so gemacht werden sollte. Wie lautet unsere Objektadresse? Warum kann es nicht einfach eine Gruppe von Textzeichenfolgen sein? Die offensichtlichsten Einwände, die auftauchen, kommen aus dem Kontext: Wer verwendet diese Adresse, in welcher Form verwenden sie sie und zu welchem Zweck? Versuchen Sie, Ihre Programmiererlogik beiseite zu legen und sich vorzustellen, wie ein "ausländischer Tourist", "Historiker", "Steuereintreiber" oder "Anwalt" denkt.
Ich vermute, Sie haben sofort eine Reihe zusätzlicher Fragen gestellt: Welche Sprache und Kodierung müssen verwendet werden, welcher Zeitraum muss berücksichtigt werden und welche Art von Dokumenten sind an dieser Operation beteiligt: legal oder postalisch? Und eine Stadt: Ist das ein benannter Ort oder was? Sogar eine Straße könnte ein Boulevard, eine Gasse, eine Allee oder etwas anderes sein. Wie sollen all diese wichtigen Details behandelt werden?
Schauen wir uns ein reales Beispiel an. Google wird jetzt von Sundar Pichai betrieben. Er kommt aus Indien. Er wurde in der Stadt Chennai geboren. Oder ist es Madras? 1996 entschieden die Einwohner, dass der Name der Stadt zu portugiesisch klang, und benannten die Hauptstadt des Bundesstaates Tamil Nadu von Madras nach Chennai um. Was sollten Sundar und seine 72 Millionen Landsleute in ihre elektronischen Dokumente eintragen?
Tatsächlich gibt es eine ganze Wissenschaft, die dies untersucht: angewandte Toponymie .
Es gibt also einige Anschlussfragen. Wie sollen
Uhrzeit und Datum behandelt werden? Was ist mit
dem Offensichtlichen, Geld ? Oder geografische Koordinaten? Und wie sollten Sie dies in Ihrem Code implementieren? Und können Sie es in ein DBMS Ihrer Wahl übertragen, ohne die Abstraktionsschicht zu verringern? Wie vermeidet man die Abwärtsspirale in atomare Arten von Maschinendaten und ständige Gedanken über deren Rekonstruktion? In diesem Fall lohnt es sich, nach der Quelle einer primitiven oder umgekehrt qualitativ hochwertigen API zu suchen. Denken Sie nach Belieben darüber nach.
Kurz gesagt, der Kontext ist das Wichtigste. Und das Objektmodell ermöglicht es uns, es direkt zu nutzen, indem wir "Maschinendaten" kapseln und kontextabhängiges "reales" Verhalten implementieren. Es ist nicht so, dass Low-Level-Tupel in Tabellen angeordnet sind ;-)
In der Zwischenzeit kehren wir zur "primitiven" Implementierung zurück und machen es uns schwerer. Zunächst werden Fehler und Duplikate beseitigt. Mit anderen Worten, wir werden nach einer Möglichkeit suchen, Adressen beim ersten Mal richtig zu schreiben. Gleichzeitig helfen wir den UI-Entwicklern, Benutzern Hinweise zu geben, wenn sie Daten in Felder eingeben.
Wenn sich zwei Dinge an einem Ort befinden - Texte und die InterSystems IRIS-Datenplattform - hat ein Entwickler die echte Möglichkeit, Dinge wirklich umzudrehen, ohne sich von der Maschine zu entfernen. Verwenden Sie beispielsweise die eingebetteten Objektkomponenten iKnow und iFind . Diese Komponenten sind für die Arbeit mit unstrukturierten Daten bzw. für die Volltextsuche vorgesehen .
Versuchen wir, das Datenschema für OpenStreetMap zu finden. Es ist nicht so einfach, wie es zunächst scheinen mag. Ich kenne den genauen Grund nicht, aber es gibt
kein Datenschema für OSM . Und es würde uns sehr helfen, wie wir weiter unten sehen werden! Und das würde das Rad nicht neu erfinden, verwenden Sie eine
geeignete XSD , die ich für Sie gefunden habe. Und danke, Oliver Schrenk.
Hier sind mehr Bilder . Ich muss sagen, dass es für unsere Zwecke geeignet ist und der internen Struktur von XML-Dateien und OSM-Downloads entspricht. Warum es wichtig ist, aber die erste Zeile in der XSD-Datei sollte mit "<? Xml ..." beginnen.
Elemente sind die Grundkomponenten des konzeptionellen Datenmodells der physischen Welt von OpenStreetMap. Sie bestehen aus
- Knoten - Punkte im Raum definieren,
- Wege - Definieren linearer Merkmale und Bereichsgrenzen und
- Beziehungen - die manchmal verwendet werden, um zu erklären, wie andere Elemente zusammenarbeiten.
Allen oben genannten können ein oder mehrere Tags zugeordnet sein (die die Bedeutung eines bestimmten Elements beschreiben). Ein Tag besteht aus zwei Elementen, einem Schlüssel und einem Wert. Tags beschreiben bestimmte Merkmale von Kartenelementen: Knoten, Wege oder Beziehungen.
Wo sind die Straßen und Städte? Es ist ein großes Geheimnis! Die Geometrie wurde gut gelehrt? Mehr dazu beim nächsten Mal. :) :)
Darüber hinaus verwenden wir den von IRIS-Entwicklern freundlicherweise für uns erstellten XSD-Schema-Assistenten für die entsprechenden Adapterfelder der Klasse% XML. Das Prozentzeichen am Anfang bedeutet nur, dass dies eine Klasse aus der Systembibliothek ist.
Weitere Informationen dazu finden Sie in der Dokumentation . Wir werden die Operationen im Terminal durchführen.
set xmlSchema = ##class(%XML.Utils.SchemaReader).%New() do xmlSchema.Process("/path/to/OSMSchema.xsd")
Sie können dasselbe von
Atelier IDE erhalten (gehen Sie im Menü zu Extras> Add-Ins> XML-Schema-Assistent):

Da wir den Assistenten verwendet haben, indem wir den Namen des resultierenden Pakets und nicht die Parameter angegeben haben, sind sie im Testpaket gelandet. Wie Sie dem zweiten Befehl entnehmen können, habe ich die Schemadatei an meinen lokalen Python-Server übergeben:
python3 -m http.server 80
Sie können jeden anderen gewünschten http-Server verwenden. Oder laden Sie die Datei auf Ihren IRIS-Server und zeigen Sie darauf.
Als Ergebnis haben wir acht Klassen, die die Struktur unserer Adress-XML vollständig widerspiegeln. Dies ist die Hauptklasse OSM.osm:
Class OSM.osm Extends (%Persistent, %XML.Adaptor) [ ProcedureBlock ] { Parameter XMLNAME = "osm"; Parameter XMLSEQUENCE = 1; Property bounds As OSM.bounds(XMLNAME = "bounds", XMLREF = 1) [ Required ]; Relationship node As OSM.node(XMLNAME = "node", XMLPROJECTION = "ELEMENT", XMLREF = 1) [ Cardinality = many, Inverse = osm ]; Relationship way As OSM.way(XMLNAME = "way", XMLPROJECTION = "ELEMENT", XMLREF = 1) [ Cardinality = many, Inverse = osm1 ]; Relationship relation As OSM.relation(XMLNAME = "relation", XMLPROJECTION = "ELEMENT", XMLREF = 1) [ Cardinality = many, Inverse = osm2 ]; Property version As %xsd.float(XMLNAME = "version", XMLPROJECTION = "ATTRIBUTE") [ InitialExpression = ".6", ReadOnly ]; Property generator As %String(MAXLEN = "", XMLNAME = "generator", XMLPROJECTION = "ATTRIBUTE") [ InitialExpression = "CGImap 0.0.2", ReadOnly ]; }
Und OSM.node:
Class OSM.node Extends (%Persistent, %XML.Adaptor) [ ProcedureBlock ] { Parameter XMLNAME = "node"; Parameter XMLSEQUENCE = 1; Relationship tag As OSM.tag(XMLNAME = "tag", XMLPROJECTION = "ELEMENT", XMLREF = 1) [ Cardinality = many, Inverse = node ]; Property id As %xsd.unsignedLong(XMLNAME = "id", XMLPROJECTION = "ATTRIBUTE"); Property lat As %xsd.double(XMLNAME = "lat", XMLPROJECTION = "ATTRIBUTE"); Property lon As %xsd.double(XMLNAME = "lon", XMLPROJECTION = "ATTRIBUTE"); Property user As %String(MAXLEN = "", XMLNAME = "user", XMLPROJECTION = "ATTRIBUTE") [ SqlFieldName = _user ]; Property uid As %xsd.unsignedLong(XMLNAME = "uid", XMLPROJECTION = "ATTRIBUTE"); Property visible As %Boolean(XMLNAME = "visible", XMLPROJECTION = "ATTRIBUTE"); Property version As %xsd.unsignedLong(XMLNAME = "version", XMLPROJECTION = "ATTRIBUTE"); Property changeset As %xsd.unsignedLong(XMLNAME = "changeset", XMLPROJECTION = "ATTRIBUTE"); Property timestamp As %TimeStamp(XMLNAME = "timestamp", XMLPROJECTION = "ATTRIBUTE"); Relationship osm As OSM.osm(XMLPROJECTION = "NONE") [ Cardinality = one, Inverse = node ]; }
Wie Sie sehen können, sind einige der Optionen, die ich bereits deaktiviert habe, für unsere Lösung nicht erforderlich.
Die Dateigröße XML nur für Russland beträgt ca. 53 GB. Sie können es nicht mit den üblichen Textverarbeitungswerkzeugen öffnen: Sie können keine so großen Dateien ertragen. Sie können kleinere Proben nehmen, um beispielsweise die Adressen Russlands für
die einzelnen Gebiete zu trainieren. Das kleine Volumen der Region Kaliningrad im komprimierten Format beträgt 18 MB, die unkomprimierte XML-Datei 203 MB.
Die maximale Länge eines Zeichenfolgenliterals in InterSystems IRIS beträgt übrigens 3.641.144 Zeichen. Mit anderen Worten, das direkte Laden einer Datei oder URL funktioniert nicht. Sie können die anderen Grenzen in der Dokumentation sehen . Um mit großen Datenmengen zu arbeiten, können Sie Datenströme ohne Längenbeschränkungen verwenden.
Mal sehen, was wir für die Menge der Knoten bekommen.
Als nächstes machen wir die Dinge
nach dem Buch . Wir erstellen ein Objekt, das XML als Muttersprache versteht, indem wir eine Klasse aus der Systembibliothek% XML verwenden. Reader:
set reader = ##class(%XML.Reader).%New()
Wir geben ihm Anweisungen, was zu nehmen ist, und ignorieren den Rest. Wir werden eine einzelne Klasse nehmen:
do reader.Correlate("node","OSM.node")
Danach gibt es verschiedene Möglichkeiten, die ursprüngliche mBD-Datei abzurufen. Wenn es Ihnen passt, können Sie es lokal im Dateisystem des IRIS-Servers neben dem Speicher-Repository ablegen. Oder fordern Sie, wie in meinem Beispiel, den Versand über HTTP an. Es gibt auch eine universellere Option, über die ich weiter unten sprechen werde.
set url="http://localhost/kaliningrad-latest.osm" write reader.OpenUrl(url)
Wichtig! An diesem Punkt werden die meisten Menschen, die dieses Beispiel selbst ausprobieren, auf etwas Schreckliches stoßen. Anstelle einer glücklichen „1“ (alles ist in Ordnung) gibt das System etwas zurück, das mit „0, STORE ...“ beginnt. Und das wird enttäuschend sein. Mit anderen Worten, die Datei mit dem scheinbar mBD erwies sich als nicht so mikro und passt nicht zu unserem Objekt. Es wurde nicht genügend Speicher zugewiesen. Kann das behoben werden? Absolut. Mit der IRIS-Datenplattform können Sie Objekte mit bis zu 4 TB im RAM erstellen. Also, was ist schief gelaufen? Standardmäßig beträgt die Größe eines Objekts in den Systemeinstellungen 256 MB. Aber wir brauchen noch viel mehr. Und denken Sie daran, dies sind RAM-Anforderungen. Haben Sie genug Platz auf Ihrem Computer / Server?
Ich habe experimentiert, um die Speichermenge zu bestimmen, die wir für diesen Riesen benötigen: fast 170 GB. Dies muss in den Einstellungen (Menü> Speicher konfigurieren>
Maximale Speicherkapazität pro Prozess (KB)) oder über
die Systemvariable $ ZSTORAGE (in Kilobyte) angegeben werden:
set $ZSTORAGE=170000000
Haben Sie einen neuen Prozess mit den richtigen Speichereinstellungen ausgeführt? Dann ist der nächste Teil einfach: Wir lesen und speichern einfach.
Es gibt auch eine alternative (und wahrscheinlich vorzuziehende) Option:
Verwenden Sie die UsePPG-Handler-Eigenschaft der% XML. Reader-Klasse, mit der Sie das XML nicht im Speicher speichern können und die mit den Standardspeichereinstellungen arbeitet.
set reader = ##class(%XML.Reader).%New() set reader.UsePPGHandler = 1
Weiter ... Korrelieren / Lesen usw. ...
do reader.Next(.object) do object.%Save()
Und so weiter, 1.180.849 Mal für jede Operation :-) Es ist langweilig. Aus diesem Grund fügen wir unsere OSM.map-Klassenmethode zum Importieren hinzu, basierend auf denselben Befehlen:
ClassMethod Import(url) { Set reader = ##class(%XML.Reader).%New() Set reader.UsePPGHandler = 1 Set status = reader.OpenURL(url) Do reader.Correlate("node","OSM.node") While (reader.Next(.object)) { Do object.%Save() } //back to top of XML file Do reader.Rewind() Do reader.Correlate("way","OSM.way") While (reader.Next(.object)) { Do object.%Save() } Do reader.Rewind() Do reader.Correlate("relation","OSM.relation") While (reader.Next(.object)) { Do object.%Save() } }
Wir werden die Leistung des Exokortex unseres Computers mit nur einem Befehl
im Terminal nutzen :
do ##class(OSM.osm).Import("http://localhost/kaliningrad-latest.osm")
Und so erhalten wir die Adressdaten offen und möglicherweise keine sehr zuverlässige Quelle. Es ist Zeit, die gleichen Phasen zu durchlaufen, aber die Daten, denen man vertrauen kann. Und auch standardisiert, aufgeräumt, gut dokumentiert und von der richtigen Regierungsbehörde erstellt: Das ist eine Sache der Legenden. Der russische Steuerdienst leistet mit seinem digitalen Produkt gute Arbeit. Soweit ein guter Job möglich ist. Natürlich hat es seine Mängel, und die Datenbereinigung wird fortgesetzt. Wie können wir das lösen? Lassen Sie die Regierungschefs darüber nachdenken. Sie treffen Entscheidungen für sich, die uns allen zugute kommen.
Und jetzt kommen wir zum Unverständlicheren - wir bringen Address bei, die richtigen Daten aus unserer Quelle zu lesen. Glücklicherweise enthält der Datensatz des Bundessteuerdienstes vorgefertigte Beschreibungen für die XML-Dokumentstruktur. Gemäß
der Beschreibung auf der FIAS-Website , die den Daten beiliegt, benötigen wir den ADDROBJ-Datensatz, der in meinem Fall der Datei AS_ADDROBJ_2_250_01_04_01_01.xsd entspricht
Verwenden Sie als Nächstes den XSD-Schema-Assistenten. Wir werden die Operationen im Terminal durchführen:
set xmlScheme = ##class(%XML.Utils.SchemaReader).%New() do xmlScheme.Process("/path/to/AS_ADDROBJ_2_250_01_04_01_01.xsd")
Daher haben wir zwei Klassen, die die Struktur unserer Adress-XML vollständig widerspiegeln:
Test.AddressObjects
/// Composition and structure of the file with classifier information for FIAS DB elements in address form Class Test.AddressObjects Extends (%Persistent, %XML.Adaptor) [ ProcedureBlock ] { Parameter XMLNAME = "AddressObjects"; Parameter XMLSEQUENCE = 1; /// Classifier for elements in address form Relationship Object As Test.Object(XMLNAME = "Object", XMLPROJECTION = "ELEMENT") [ Cardinality = many, Inverse = AddressObjects ]; }
Testobjekt
/// Created from: http://localhost:28869/AS_ADDROBJ_2_250_01_04_01_01.xsd Class Test.Object Extends (%Persistent, %XML.Adaptor) [ ProcedureBlock ] { Parameter XMLNAME = "Object"; Parameter XMLSEQUENCE = 1; /// Global unique identifier of the address object Property AOGUID As %String(MAXLEN = 36, MINLEN = 36, XMLNAME = "AOGUID", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Formal name Property FORMALNAME As %String(MAXLEN = 120, MINLEN = 1, XMLNAME = "FORMALNAME", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Region code Property REGIONCODE As %String(MAXLEN = 2, MINLEN = 2, XMLNAME = "REGIONCODE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Autonomy code Property AUTOCODE As %String(MAXLEN = 1, MINLEN = 1, XMLNAME = "AUTOCODE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Area code Property AREACODE As %String(MAXLEN = 3, MINLEN = 3, XMLNAME = "AREACODE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// City code Property CITYCODE As %String(MAXLEN = 3, MINLEN = 3, XMLNAME = "CITYCODE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Code of area within city Property CTARCODE As %String(MAXLEN = 3, MINLEN = 3, XMLNAME = "CTARCODE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Locality code Property PLACECODE As %String(MAXLEN = 3, MINLEN = 3, XMLNAME = "PLACECODE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Planning structure element code Property PLANCODE As %String(MAXLEN = 4, MINLEN = 4, XMLNAME = "PLANCODE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Street code Property STREETCODE As %String(MAXLEN = 4, MINLEN = 4, XMLNAME = "STREETCODE", XMLPROJECTION = "ATTRIBUTE"); /// Code of additional element in address form Property EXTRCODE As %String(MAXLEN = 4, MINLEN = 4, XMLNAME = "EXTRCODE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Code of subordinate additional element in address form Property SEXTCODE As %String(MAXLEN = 3, MINLEN = 3, XMLNAME = "SEXTCODE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Official name Property OFFNAME As %String(MAXLEN = 120, MINLEN = 1, XMLNAME = "OFFNAME", XMLPROJECTION = "ATTRIBUTE"); /// Postal code Property POSTALCODE As %String(MAXLEN = 6, MINLEN = 6, XMLNAME = "POSTALCODE", XMLPROJECTION = "ATTRIBUTE"); /// Federal Tax Service - Private Individual code Property IFNSFL As %String(MAXLEN = 4, MINLEN = 4, XMLNAME = "IFNSFL", XMLPROJECTION = "ATTRIBUTE"); /// Federal Tax Service - Private Individual territorial district code Property TERRIFNSFL As %String(MAXLEN = 4, MINLEN = 4, XMLNAME = "TERRIFNSFL", XMLPROJECTION = "ATTRIBUTE"); /// Federal Tax Service - Legal Entity code Property IFNSUL As %String(MAXLEN = 4, MINLEN = 4, XMLNAME = "IFNSUL", XMLPROJECTION = "ATTRIBUTE"); /// Federal Tax Service - Legal Entity territorial district code Property TERRIFNSUL As %String(MAXLEN = 4, MINLEN = 4, XMLNAME = "TERRIFNSUL", XMLPROJECTION = "ATTRIBUTE"); /// Russian Classification on Objects of Administrative Division Property OKATO As %String(MAXLEN = 11, MINLEN = 11, XMLNAME = "OKATO", XMLPROJECTION = "ATTRIBUTE"); /// Russian Classification of Territories of Municipal Formations Property OKTMO As %String(MAXLEN = 11, MINLEN = 8, XMLNAME = "OKTMO", XMLPROJECTION = "ATTRIBUTE"); /// Date of record entry Property UPDATEDATE As %Date(XMLNAME = "UPDATEDATE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Short name of object type Property SHORTNAME As %String(MAXLEN = 10, MINLEN = 1, XMLNAME = "SHORTNAME", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Address object level Property AOLEVEL As %Integer(XMLNAME = "AOLEVEL", XMLPROJECTION = "ATTRIBUTE", XMLTotalDigits = 10) [ Required ]; /// Object identifier of the parent object Property PARENTGUID As %String(MAXLEN = 36, MINLEN = 36, XMLNAME = "PARENTGUID", XMLPROJECTION = "ATTRIBUTE"); /// Unique record identifier. Key field. Property AOID As %String(MAXLEN = 36, MINLEN = 36, XMLNAME = "AOID", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Record identifier associated with previous historical record Property PREVID As %String(MAXLEN = 36, MINLEN = 36, XMLNAME = "PREVID", XMLPROJECTION = "ATTRIBUTE"); /// Record identifier associated with next historical record Property NEXTID As %String(MAXLEN = 36, MINLEN = 36, XMLNAME = "NEXTID", XMLPROJECTION = "ATTRIBUTE"); /// Address object code in one string with validity indicator from Russian Classifier of Addresses (KLADR) 4.0. Property CODE As %String(MAXLEN = 17, MINLEN = 0, XMLNAME = "CODE", XMLPROJECTION = "ATTRIBUTE"); /// Address object code from KLADR 4.0 in one string without validity indicator (last two digits) Property PLAINCODE As %String(MAXLEN = 15, MINLEN = 0, XMLNAME = "PLAINCODE", XMLPROJECTION = "ATTRIBUTE"); /// Validity status of FIAS address object. Current address as of today's date. Usually the last entry about the address object. /// 0 - Not current /// 1 - Current Property ACTSTATUS As %Integer(XMLNAME = "ACTSTATUS", XMLPROJECTION = "ATTRIBUTE", XMLTotalDigits = 10) [ Required ]; /// Center status Property CENTSTATUS As %Integer(XMLNAME = "CENTSTATUS", XMLPROJECTION = "ATTRIBUTE", XMLTotalDigits = 10) [ Required ]; /// Operation status on record - reason for record's appearance (see description of OperationStatus table): /// 01 – Activation; /// 10 – Addition; /// 20 – Change; /// 21 – Group change; /// 30 – Deletion; /// 31 - Deletion due to the deletion of the parent object; /// 40 – Attachment of the address object (merger); /// 41 – Reassignment due to the merger of the parent object; /// 42 - Termination due to the attachment to another address object; /// 43 - Creation of a new address object due to a merger of address objects; /// 50 – Reassignment; /// 51 – Reassignment due to the reassignment of the parent object; /// 60 – Termination due to segmentation; /// 61 – Creation of a new address object due to segmentation Property OPERSTATUS As %Integer(XMLNAME = "OPERSTATUS", XMLPROJECTION = "ATTRIBUTE", XMLTotalDigits = 10) [ Required ]; /// KLADR 4 validity status (last two digits in the code) Property CURRSTATUS As %Integer(XMLNAME = "CURRSTATUS", XMLPROJECTION = "ATTRIBUTE", XMLTotalDigits = 10) [ Required ]; /// Start of record operation Property STARTDATE As %Date(XMLNAME = "STARTDATE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// End of record operation Property ENDDATE As %Date(XMLNAME = "ENDDATE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Foreign key to requirements document Property NORMDOC As %String(MAXLEN = 36, MINLEN = 36, XMLNAME = "NORMDOC", XMLPROJECTION = "ATTRIBUTE"); /// Current address object indicator Property LIVESTATUS As %xsd.byte(VALUELIST = ",0,1", XMLNAME = "LIVESTATUS", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Address type: /// 0 - not defined /// 1 - municipal; /// 2 - administrative/territorial Property DIVTYPE As %xsd.int(VALUELIST = ",0,1,2", XMLNAME = "DIVTYPE", XMLPROJECTION = "ATTRIBUTE") [ Required ]; Relationship AddressObjects As Test.AddressObjects(XMLPROJECTION = "NONE") [ Cardinality = one, Inverse = Object ]; }
Aus der gesamten Liste der XML-Dateien in FIAS wird nur die Datei mit den Namen von Regionen, Städten und Straßen verwendet. Als ich mich auf die Veröffentlichung vorbereitete, hatte ich diese:
AS_ADDROBJ_20190106_90809714-fe22-45b2-929c-52bd950963e0.XML
Lassen Sie uns etwas FIAS gefüllten Pfeffer kochen. Dies ist nur eine Vorbereitung auf eine große Zukunft. Zuerst erhalten wir den anfänglichen Mindestsatz. Dies sind die einzigen Zutaten, die wir brauchen:
Class FIAS.AddressObject Extends (%Persistent, %XML.Adaptor) [ ProcedureBlock ] { Parameter XMLNAME = "Object"; Parameter XMLSEQUENCE = 1; /// Global unique identifier of the address object Property AOGUID As %String(MAXLEN = 36, MINLEN = 36, XMLNAME = "AOGUID", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Official name Property OFFNAME As %String(MAXLEN = 120, MINLEN = 1, XMLNAME = "OFFNAME", XMLPROJECTION = "ATTRIBUTE"); /// Postal code Property POSTALCODE As %String(MAXLEN = 6, MINLEN = 6, XMLNAME = "POSTALCODE", XMLPROJECTION = "ATTRIBUTE"); /// Short name of object type Property SHORTNAME As %String(MAXLEN = 10, MINLEN = 1, XMLNAME = "SHORTNAME", XMLPROJECTION = "ATTRIBUTE") [ Required ]; /// Address object level Property AOLEVEL As %Integer(XMLNAME = "AOLEVEL", XMLPROJECTION = "ATTRIBUTE", XMLTotalDigits = 10) [ Required ]; /// Object identifier of the parent object Property PARENTGUID As %String(MAXLEN = 36, MINLEN = 36, XMLNAME = "PARENTGUID", XMLPROJECTION = "ATTRIBUTE"); /// Unique record identifier. Key field. Property AOID As %String(MAXLEN = 36, MINLEN = 36, XMLNAME = "AOID", XMLPROJECTION = "ATTRIBUTE") [ Required ]; }
Wir erstellen ein Objekt, das XML als Muttersprache versteht, indem wir eine Klasse aus der Systembibliothek% XML verwenden. Reader:
set reader = ##class(%XML.Reader).%New()
Wir werden ihm Anweisungen geben, wen er nehmen soll, und ihm sagen, dass er den Rest ignorieren soll. Wir nehmen eine einzelne Portion zum Testen.
Weiter ... Korrelieren / Lesen usw. ...
do reader.Correlate("Object","FIAS.AddressObject") set url="http://localhost/AS_ADDROBJ_20190106_90809714-fe22-45b2-929c-52bd950963e0.XML" write reader.OpenUrl(url)
Dann ist der nächste Teil einfach: Wir lesen und speichern einfach.
do reader.Next(.object) do object.%Save()
Und so weiter, 3.722.548 Mal für jede Operation :-)
Es ist noch anstrengender als zuvor. Aus diesem Grund fügen wir unsere FIAS.AddressObject-Klassenmethode zum Importieren hinzu, basierend auf denselben Befehlen:
ClassMethod Import() { // Create object to read XML Set reader = ##class(%XML.Reader).%New() // Get source XML for parsing Set status = reader.OpenURL("http://localhost/AS_ADDROBJ_20190106_90809714-fe22-45b2-929c-52bd950963e0.XML") If $$$ISERR(status) {Do $System.Status.DisplayError(status)} // Join object with the right sample structure Do reader.Correlate("Object","FIAS.AddressObject") // Read and save the object in storage While (reader.Next(.object,.status)) { Set status = object.%Save() If $$$ISERR(status) {do $System.Status.DisplayError(status)} } // If an error occurs during parsing, display a message If $$$ISERR(status) {Do $System.Status.DisplayError(status) }
Wir werden die Leistung des Exokortex unseres Computers mit nur einem Befehl
im Terminal nutzen :
do ##class(FIAS.AddressObject).Import()

Das Abendessen ist fertig, alle zusammen. Es war mBD, und jetzt ist es ein fertiges Gericht, ein globales Gericht mit den verifizierten Namen russischer Städte.

Und zum Schluss noch ein paar Worte darüber, was zu tun ist, wenn 4 TB nicht ausreichen. In diesem Fall verwenden
wir die Streams . Alles ist in der Dokumentation beschrieben. Sie können Binärdateien oder Zeichen verwenden. Das Speichern in einem globalen Format ist ebenfalls möglich. Hier ist das Rezept: Nehmen Sie den Stream, schneiden Sie ihn in Stücke und weisen Sie ihn den Objekten zu, die wir zum Verzehr benötigen.
Hier war nicht genug Platz für mehr über die schönen Adress-ObjectScript-Objekte und die Python-API. Das wird eine andere Geschichte sein.
Gute Nachrichten: Gartner hat gerade seine jährliche Sammlung realer Benutzerbewertungen und Rückmeldungen in der Kategorie DBMS abgeschlossen und diese Informationen verwendet, um seine Rangliste der besten DBMS von 2019 zu veröffentlichen. InterSystems Caché und InterSystems IRIS Data Platform erhielten die höchste Bewertung für "Kunden" 'Wahl. " Sie können überprüfen, welche Produkte berücksichtigt wurden und wie sie bewertet wurden.
Beste Software für betriebliche Datenbankverwaltungssysteme von 2019, wie von Kunden bewertet.
