Vejledning om Kom godt igang med Elasticsearch

Seneste opdatering 3. november 2025

Når du har fået adgang til CVR data (dvs. du har fået udleveret dit brugerId og password) kan du begynde at fremsøge CVR data fra indekset i form af JSON dokumenter.

  • Version 1 
  • Ansvarlig [email protected]

Indledning

Inden du går i gang, vil vi ikke undlade at gøre opmærksom på, at Elasticsearch produktet har en omfattende referencedokumentation. Du vil med fordel kunne læse denne, inden du evt. forsøger dig frem, idet du hermed får en bedre forståelse for, hvad der er muligt, hvilket igen kan være med til at inspirere dig til lave en bedre løsning, her forstået som en løsning, der arbejder hurtigt, og som udnytter Elasticsearch på en optimal måde. 

Hvis du laver en løsning der ikke er optimal, i forhold til vores drift af Elasticsearch, kan du komme ud for, at vi midlertidigt bliver nødt til at spærre for din adgang.

Kapitel
1
Adgang og begrænsninger

Du har kun fået adgang til at fremsøge data via RESTful API’et, så du kan koncentrere dig om at forstå Elasticsearch forespørgsler, dvs. de forskellige typer af forespørgsler og det tilhørende forespørgsels-sprog (Query DSL).

For tiden anvender vi Elasticsearch v.6.8.23. Som ekstern bruger har man som sagt kun adgang til at fremsøge data, hvilket via RESTful API’et gøres ved at sende enten en http GET eller en POST request til /indeksnavn/type1 /_search endepunktet på frontend serveren, der sørger for at sende requestet videre til Elasticsearch, vente på svaret og returnere det til brugeren.

Kapitel
2
Anbefaling: Brug Query DSL

Man kan lave simple forespørgsler via GET request parametre, men det anbefales, at man bruger Elasticsearch query DSL sproget, som sendes i body af request’et (både GET og POST er understøttet). 

Query DSL giver mange flere muligheder, ligesom man enkelt kan begrænse størrelsen af svaret man modtager. Der jo ikke nogen grund til at modtage alle registrerede data om en virksomhed (også historiske), hvis man kun har behov for virksomhedens aktuelle navn.

2.1. Eksempel på simpel forespørgsel

Eksemplet viser: søg efter en virksomhed med cvr nr. 12345678 og returner virksomhedens aktuelle navn.

Eksempel

Eksempel på en forespørgsel

POST /cvr-permanent/virksomhed/_search
{
 "_source":["Vrvirksomhed.virksomhedMetadata.nyesteNavn.navn"],
 "query":{"term":{"Vrvirksomhed.cvrNummer":"12345678"}}
}

Hvis der ikke findes et virksomhed type dokument, der opfylder søgebetingelserne, kommer der et svar som vist i eksemplet:

Eksempel

Eksempel på forespørgsel uden resultater

{"took":1,"timed_out":false,"_shards":{"total":3,"successful":3,"failed":0},"hits":{"total":0,"max_score":null,"hits":[]}}

Det interessante er sektionen: "hits":{"total":0, …}, der viser hvor mange dokumenter, der opfylder søgekriteriet.

2.2. Afprøvning af eksempler med værktøjer

Hvis man selv vil afprøve nogen af de viste eksempler, kan det gøres med fx kommando-linje værktøjet curl eller browser-plugin som fx Postman. 

Værktøjet skal understøtte afsendelse af et http request med Authorization header, Content-Type header og en request body med forespørgslen.

Eksempel

Eksempel med curl (her bruges CVR-nummer 30714024)

curl -XPOST -u 'BrugerId:Password' –H 'Content-Type: application/json' http://distribution.virk.dk/cvr-permanent/virksomhed/_search -d '
{
 "_source":["Vrvirksomhed.virksomhedMetadata.nyesteNavn.navn"],
 "query":{"term":{"Vrvirksomhed.cvrNummer":"30714024"}}
}' | python -mjson.tool

Eksemplet viser hvordan svaret gennem python JSON fortolkeren via en pipe, så vi får et pænt formateret resultat.

Eksempel

Eksempel på pænt formateret resultat via Python JSON fortolkeren

{
   "_shards": {
       "failed": 0,
       "skipped": 0,
       "successful": 6,
       "total": 6
   },
   "hits": {
       "hits": [
           {
               "_id": "4001646987",
               "_index": "cvr-v-20200115",
               "_score": 11.955761,
               "_source": {
                   "Vrvirksomhed": {
                       "virksomhedMetadata": {
                           "nyesteNavn": {
                               "navn": "Nine A/S"
                           }
                       }
                   }
               },
               "_type": "_doc"
           }
       ],
       "max_score": 11.955761,
       "total": 1
   },
   "timed_out": false,
   "took": 3
}

Eksemplet viser at – som forventet - der er fundet et resultat ("total": 1) som der kun returnerer virksomhedens navn: "NINE A/S" - som vi bad om. 

Prøv evt. at gentage forespørgslen, men denne gang uden linjen: "_source":["Vrvirksomhed.virksomhedMetadata.nyesteNavn.navn"], så du kan se alle data, der er registreret om virksomheden.

Kapitel
3
Dataregistrering og Validering: To Hovedscenarier

Når vi ser på hvordan data anvendes, er der to typiske hovedscenarier:

  1. Simpel validering og opslag
  2. Søgning på baggrund af virksomhedsnavn.

3.1. Simpel validering og opslag

Det ene er, hvor man typisk ønsker at lave simple valideringer af dataregistreringer i eget system, hvor man derfor f.eks. ønsker at validere et indtastet CVR-nummer og vise det tilhørende virksomhedsnavn. 

Det kan også være hjælp til at finde et CVR-nummer på basis af et indtastet virksomhedsnavn, eller en del af et indtastet virksomhedsnavn. 

For begge eksempler har man oftest kun behov for at validere i forhold til de aktuelle virksomhedsdata, og man vil derfor med fordel kunne søge i "Vrvirksomhed.virksomhedMetadata" sektionen i virksomhed typen i Elasticsearch CVR-permanent indekset (som i eksemplet).

3.2. Søgning på baggrund af virksomhedsnavn

Man kan også finde et CVR-nummer ud fra et virksomhedsnavn.

Eksempel

Eksempel på at finde et CVR-nummer via et virksomhedsnavn

POST /cvr-permanent/virksomhed/_search
{
 "_source":[
   "Vrvirksomhed.cvrNummer",
   "Vrvirksomhed.virksomhedMetadata.nyesteNavn.navn"
 ],
 "query":{
   "query_string":{
     "default_field":"Vrvirksomhed.virksomhedMetadata.nyesteNavn.navn",
     "query":"parken AND sport"
   }
 },
 "size":20
}

Søgningen vil returnere følgende svar (det var i hvert fald tilfældet, da dette blev skrevet):

Eksempel

Eksempel på fremsøgning af CVR-nummer med virksomhedsnavn

{
   "_shards": {
       "failed": 0,
       "skipped": 0,
       "successful": 6,
       "total": 6
   },
   "hits": {
       "hits": [
           {
               "_id": "2834786",
               "_index": "cvr-v-20200115",
               "_score": 16.309305,
               "_source": {
                   "Vrvirksomhed": {
                       "cvrNummer": 15107707,
                       "virksomhedMetadata": {
                           "nyesteNavn": {
                               "navn": "PARKEN SPORT & ENTERTAINMENT A/S"
                           }
                       }
                   }
               },
               "_type": "_doc"
           }
       ],
       "max_score": 16.309305,
       "total": 1
   },
   "timed_out": false,
   "took": 7
}

Her skal man forestille sig at brugeren har indtastet ”parken sport”, hvilket i ”query” delen udtrykkes som begge ord skal findes (AND) i attributten ”Vrvirksomhed.virksomhedMetadata.nyesteNavn.navn”. 

Det er også muligt at anvende wild-card søgninger med query_string operatoren. Eksemplet viser også at det var tilstrækkeligt for brugeren at indtaste ”parken sport”, idet der kun blev fundet et resultat, der opfyldte søgekriteriet. 

En anden ting man skal bemærke er ”size” attributten i forespørgslen, der beskriver hvor mange dokumenter, man maksimalt vil have i resultatet (standardværdien er 10), og nej, man kan ikke få alle dokumenter tilbage i en søgning, ved at specificere et tilstrækkeligt stort tal.

Kapitel
4
Lokal kopi af virksomhedsdata (Scroll søgning)

Et andet scenarie er at man ønsker af forskellige grunde, at have fx et udsnit af alle virksomhedsdata liggende i lokal kopi. 

Der skal man lave en søgning, der giver en mulighed for at hente alle de ønskede data, og at man med mellemrum skal fremsøge ændringer, i hvert fald hvis man vil holde sin egen lokale kopi opdateret.

Til dette formål skal man anvende en scroll søgning, hvor Elasticsearch returnerer virksomhedsdata i passende klumper, svarende til ”size” attributten. Elasticsearch læser data transaktionelt, således at man vil kunne gemme starttidspunktet for scroll søgningen, som efterfølgende vil kunne anvendes som starttidspunkt, når man senere skal fremsøge ændringer, så man kan vedligeholde den lokale kopi.

Hvis ens lokale register fx indeholder CVR-nummer., aktuelt navn og adresse, kan man udføre en scroll søgning som i eksemplet:

Eksempel

POST /cvr-permanent/virksomhed/_search?scroll=1m
{
 "_source":[
   "Vrvirksomhed.cvrNummer",
   "Vrvirksomhed.virksomhedMetadata.nyesteNavn.navn",
   "Vrvirksomhed.virksomhedMetadata.nyesteBeliggenhedsadresse"
 ],
 "query":{"match_all":{}},
 "size":100
}

4.1. Håndtering af scroll og ressourcer

Resultatet af denne første søgning er et scroll_id samt første klump af de ønskede data. Inden udløbet af scroll perioden (i eksemplet ovenfor 1m = 1 minut som er den anbefalede værdi), skal man have lavet det næste kald igen, med det senest modtagne scroll_id. Dvs. så længe man bliver ved med at kalde indenfor scroll perioden holder Elasticsearch læse transaktionen aktiv. 

Man bliver ved med at kalde indtil man ikke længere modtager nogen hits, dvs. indtil hits array er tomt (i hvert fald hvis man ønsker at modtage alle dokumenter, der opfylder søgekriterierne). Hvis man har udfordringer med at nå at behandle alle de modtagne dokumenter indenfor et minut, er vores anbefaling klart at man nedjusterer size, fremfor at øge længden af scroll perioden. Vi forbeholder os retten til at spærre adgangen til Elasticsearch løsningen, hvis man ikke følger retningslinjerne.

De efterfølgende søgninger, der skal udføres, har alle formen som i eksemplet:

Eksempel

Eksempel på efterfølgende søgninger

GET /_search/scroll
{
   "scroll":"1m","scroll_id": "c2NhbjszOzEyMTk2NDcyOkd6ZVlKRlVOVHF5UkkwNVZLM2Rhb0E7MTIxMTEzNTU6ei1JaXRWWXhTenF5SElUTTFHRk9OdzsxMjE5NjQ3MzpHemVZSkZVTlRxeVJJMDVWSzNkYW9BOzE7dG90YWxfaGl0czoxNTY2Mjk3Ow=="
}

Så længe ens scroll transaktion er aktiv vil kaldet returnerer det næste scroll_id og den næste klump data som i eksemplet:

Eksempel

Eksempel på data fra aktiv scroll transaktion

{
 "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBgAAAAAsHP8RFnJJU1NwRnh2VGkyWTBtQVJPckZlZ0EAAAAAKy2PExY2REFHMndSc1FRdUc4QVljY0xEdVlBAAAAACwc_xIWcklTU3BGeHZUaTJZMG1BUk9yRmVnQQAAAAAsHP8TFnJJU1NwRnh2VGkyWTBtQVJPckZlZ0EAAAAAKy3lbxZsOVJReVVYVVR0cVFuTWJyN2JMSzZRAAAAACst5W4WbDlSUXlVWFVUdHFRbk1icjdiTEs2UQ==",
 "_shards": {
   "failed": 0,
   "skipped": 0,
   "successful": 6,
   "total": 6
 },
 "hits": {
   "hits": [
     {
       "_id": "4006195717",
       "_index": "cvr-v-20200115",
       "_score": 1.0,
       "_source": {
         "Vrvirksomhed": {
           "cvrNummer": 36936126,
           "virksomhedMetadata": {
             "nyesteBeliggenhedsadresse": {
             "adresseId": "0a3f509f-ecbf-32b8-e044-0003ba298018",
             "bogstavFra": null,
             "bogstavTil": null,
             "bynavn": null,
             "conavn": "Greencubator",
             "etage": "1",
             "fritekst": null,
             "husnummerFra": 20,
             "husnummerTil": null,
             "kommune": {
               "kommuneKode": 101,
               "kommuneNavn": "K\u00d8BENHAVN",
               "periode": {
                 "gyldigFra": "2007-01-01",
                 "gyldigTil": null
               },
               "sidstOpdateret": "2006-11-13T00:00:00.000+01:00"
             },
             "landekode": "DK",
             "periode": {
               "gyldigFra": "2015-11-21",
               "gyldigTil": "2016-09-26"
             },
             "postboks": null,
             "postdistrikt": "K\u00f8benhavn N",
             "postnummer": 2200,
             "sidedoer": null,
             "sidstOpdateret": "2016-10-07T12:47:22.000+02:00",
             "sidstValideret": "2019-02-18T15:09:32.102+01:00",
             "vejkode": 5192,
             "vejnavn": "N\u00f8rrebrogade"
             },
             "nyesteNavn": {
               "navn": "N\u00f8ddebazaren IVS"
             }
           }
         }
       },
       "_type": "_doc"
     },
     .
     . (her er der klippet 98 virksomheder ud af resultatet)
     .,
     {
       "_id": "4006406570",
       "_index": "cvr-v-20200115",
       "_score": 1.0,
       "_source": {
         "Vrvirksomhed": {
           "cvrNummer": 37337625,
           "virksomhedMetadata": {
           "nyesteBeliggenhedsadresse": {
             "adresseId": "0a3f50b3-fd0f-32b8-e044-0003ba298018",
             "bogstavFra": null,
             "bogstavTil": null,
             "bynavn": null,
             "conavn": null,
             "etage": null,
             "fritekst": null,
             "husnummerFra": 90,
             "husnummerTil": null,
             "kommune": {
               "kommuneKode": 461,
               "kommuneNavn": "ODENSE",
               "periode": {
                 "gyldigFra": "2007-01-01",
                 "gyldigTil": null
               },
               "sidstOpdateret": "2006-11-13T00:00:00.000+01:00"
             },
             "landekode": "DK",
             "periode": {
               "gyldigFra": "2016-12-05",
               "gyldigTil": "2019-09-28"
             },
             "postboks": null,
             "postdistrikt": "Odense C",
             "postnummer": 5000,
             "sidedoer": null,
             "sidstOpdateret": "2019-09-28T09:07:11.000+02:00",
             "sidstValideret": "2017-09-30T10:31:48.423+02:00",
             "vejkode": 3143,
             "vejnavn": "Henriettevej"
           },
           "nyesteNavn": {
             "navn": "Prescott Hypnose v/John Prescott"
           }
         }
       },
       "_type": "_doc"
     }
   ],
   "max_score": 1.0,
   "total": 1854028
 },
 "timed_out": false,
 "took": 47
}

Når man er færdig med sit scroll, skal man huske at slette det igen (clear scroll), så Elasticsearch hurtigst muligt kan frigive allokerede ressourcer. Dette gøres ved at sende en DELETE request med det sidst modtagne scroll_id.

Eksempel

Eksempel sletning af scroll

DELETE /_search/scroll
{
 "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBgAAAAAsHP8RFnJJU1NwRnh2VGkyWTBtQVJPckZlZ0EAAAAAKy2PExY2REFHMndSc1FRdUc4QVljY0xEdVlBAAAAACwc_xIWcklTU3BGeHZUaTJZMG1BUk9yRmVnQQAAAAAsHP8TFnJJU1NwRnh2VGkyWTBtQVJPckZlZ0EAAAAAKy3lbxZsOVJReVVYVVR0cVFuTWJyN2JMSzZRAAAAACst5W4WbDlSUXlVWFVUdHFRbk1icjdiTEs2UQ=="
}
 

Scroll eksempel forespørgslen, der blev vist før, henter alle virksomheder (der bruges en match_all query), så man får data om alle virksomheder også de historiske. 

Hvis man kun ønsker data om aktive virksomheder, kan man i stedet bruge queryen i eksemplet::

Eksempel

Eksempel på søgning efter data om aktive virksomheder

POST /cvr-permanent/virksomhed/_search?scroll=1m
{
 "_source":[
   "Vrvirksomhed.cvrNummer",
   "Vrvirksomhed.virksomhedMetadata.nyesteNavn.navn",
   "Vrvirksomhed.virksomhedMetadata.nyesteBeliggenhedsadresse"
 ],
 "query":{
   "nested":{
     "path":"Vrvirksomhed.livsforloeb",
     "query":{
       "bool":{
         "must_not":[
           {"exists":{
             "field":"Vrvirksomhed.livsforloeb.periode.gyldigTil"
           }}
         ]
       }
     }
   }
 },
 "size":100
}

Eksemplet viser, at så længe en virksomhed har et livsforløb med en åben periode (gyldigTil mangler), er den at betragte som aktiv. 

Queryen i eksemplet er nødt til at anvende en nested query, da der findes virksomhedsformer med flere livsforløb fx enkeltmandsvirksomheder.

Hermed er der ikke sagt noget om virksomhedens status, om den er fx er normal eller under konkurs (se Vrvirksomhed.virksomhedMetadata.sammensatStatus der indeholder den aktuelle værdi).

4.2. Filtrering på aktive virksomheder

Når man skal hente opdateringer og nyoprettelser til sin lokale kopi, skal man igen lave en scroll søgning, hvor man skal fremsøge de virksomheder, der har et Vrvirksomhed.sidstIndlaest tidspunkt, som er større end eller lig med det tidspunkt, man gemte fra sidst man opdaterede. 

Den kan fx se således ud:

Eksempel

Eksempel på søgning med tidspunkt for sidste opdatering

POST /cvr-permanent/virksomhed/_search?scroll=1m
{
 "_source":[
   "Vrvirksomhed.cvrNummer",
   "Vrvirksomhed.virksomhedMetadata.nyesteNavn.navn",
   "Vrvirksomhed.virksomhedMetadata.nyesteBeliggenhedsadresse"
 ],
 "query":{
   "range":{
     "Vrvirksomhed.sidstIndlaest":{
       "gte":"2016-11-20T13:23:31.000"
     }
   }
 },
 "size":100
}

Her skal man blot være opmærksom på, at selv om virksomheden er blevet indlæst igen, er det ikke sikkert, at virksomheden faktisk er opdateret, ej heller at en evt. opdatering vedrører det udsnit af data, man har i sin lokale kopi, så ovenstående fremsøger sikkert for meget data, men til gengæld er det et simpelt søgeudtryk, og man er sikker på ikke at gå glip af nogen opdateringer.

Det er naturligvis muligt kun at fremsøge fx ændrede navne og/eller adresser via nested queries. 

Kapitel
5
Søgning på fx produktionsenhed og deltager

De viste eksempler har alle taget udgangspunkt i virksomhed typen, der også indeholder referencer til produktionsenheder, som man kan finde alle detaljer om ved at søge i produktionsenhed typen på VrproduktionsEnhed.pNummer

Tilsvarende indeholder virksomhed typen også informationer om deltagerne, men også her gælder det, at den fulde information om en deltager findes i deltager typen ved at søge på Vrdeltager.enhedsNummer.

Bilag

Bilag A Søgeeksempler i registreringstekster, ElasticSearch v.6.x

Dette bilag beskriver hvilken URL der skal anvendes, samt de dertilhørende basale POST-requests når der søges i Erhvervsstyrelsens ”System-til-system adgang til Registreringstekster”.

Søgning på CVR-nummer

BODY:
{
"query": {
"bool": {
"must": [
{
"term": {
"cvrNummer": "VALUE"
}
}
]
}
}
}

Søgning på offentliggørelsesperiode

BODY:
{
"query": {
"bool": {
"must": [
{
"range": {
"offentliggoerelseTidsstempel": {
"gte": "2020-09-04T00:00:01.000Z",
"lte": "2020-09-05T00:00:01.000Z"
}
}
}
]
}
}
}

Søgning på registreringsstatus

BODY:
{
"query": {
"bool": {
"must": [
{
"query_string": {
"default_field": "virksomhedsregistreringstatusser.keyword",
"query": "ANDET"
}
}
]
}
}
}
VÆRDISÆT for "virksomhedsregistreringstatusser.keyword"
”ANDET”
”AENDRING_KAPITAL”
”AENDRING_ADRESSE”
”AENDRING_REVISION”
”AENDRING_PERSONKREDS”
”AENDRING_STATUS”
”OMDANNELSE”
”NYT_SELSKAB”
”RETTELSE”
"FUSION_SPALTNING_OFFENTLIGGOERELSE"
"FUSION_SPALTNING_GENNEMFOERELSE"
"UOVERENSSTEMMELSE_REELLE_EJERE"