U bent nu hier: Home Blog Trek data uit een tabel en ga gratis naar HAR2009

Trek data uit een tabel en ga gratis naar HAR2009

Gepubliceerd:
13/01/2009 om 07:23
Door: Brenno de Winter
34 reacties

Het wil maar niet lukken de zogenaamde vier euro-transacties uit de database van Trans Link te halen en te presenteren. Na maanden knutselen zijn de deskundigen er nog steeds niet uit. Volgens ingewijden wordt er gewoon gewerkt met een SQL-database en dus zou je toch zeggen dat het niet zo bijster ingewikkeld kan zijn. Dat willen we bij Webwereld eens uitproberen en we dagen jullie dus uit! Degene die het eerst een goed werkend script aanlevert dat werkt op MS Access of MySQL krijgt een gratis toegangskaart voor de hackersconferentie HAR 2009. Degene die niet het snelst is, maar de – in mijn subjectieve ogen – mooiste oplossing aanlevert krijgt een anonieme OV-chipkaart met vier euro. Oplossingen kunnen in de comments gepost worden, zodat mensen elkaars werk weer kunnen verbeteren.

De casus: Een transactie begint of eindigt op een punt (halte of station). Wie wel incheckt betaalt vier euro. Bij het uitchecken wordt het teveel betaalde teruggeboekt. Dat ziet er zo uit:

DATUM LOCATIE Vervoersbedrijf Bedrag
13-01-2009 07:06 Amsterdam Centraal Station GVB -4.00
13-01-2009 07:15 Amsterdam Nieuwmarkt GVB 3.30
13-01-2009 09:37 Rotterdam Blaak RET -4.00

Codering

Natuurlijk staat niet de naam van het vervoersbedrijf in de transactietabel, maar een ID wat ook voor de locatie geldt. Een tabel ziet er dan ook zo uit:

  • TransactieID;
  • Tijdstip (datum/tijd);
  • KaartID (16 cijfers);
  • LocatieID (10 cijfers);
  • PoortjesID
  • Bedrag (currency-veld);

Vier-euro transactie

Nu is een vier euro transactie een transactie, waar geen creditering op volgt. Het probleem is wel dat de database van TLS de transacties uit het land niet in de juiste volgorde binnenkrijgt. Ook is het mogelijk dat sommige mensen wel uitchecken, maar niet inchecken (dat zijn mensen met een abonnement). Deze moet je dus niet meetellen, want anders krijg je een vertekend beeld. In principe zien de regionale vervoersbedrijven een transactie als afgerond binnen anderhalf uur (maximale overstaptijd).

Mooi maken

Wie niet alleen gaat voor het gratis ticket, maar ook voor de OV-chipkaart met vier euro kan andere tabellen toevoegen en beginnen met het vergaren van heuse stationscodes. Ook een opdeling per maand maakt natuurlijk de queries mooier.

Ingewikkeld

De opdracht is kennelijk heel ingewikkeld, want vanaf oktober proberen de RET dit overzicht te leveren, maar wil het maar niet lukken. Aan de echte hacker de missie dit sneller te doen. Dit hoeven geen mooie rapportages te zijn, want de Tweede Kamer wil gewoon een getal weten.

Mocht iemand voor woensdag 14 januari 2009 12:00 uur een oplossing hebben dan wordt deze symbolisch aan SP-kamerlid Emile Roemer overhandigt voor de hoorzitting OV-chipkaart.

34 reacties op Trek data uit een tabel en ga gratis naar HAR2009

Rinze op 13-01-2009 om 08:57

Voor MS Access:

SELECT COUNT(*) AS [Aantal 4 Euro Transacties]
FROM transacties AS t1
WHERE t1.Bedrag = -4
AND (
SELECT TOP 1 t2.Bedrag
FROM transacties t2
WHERE t2.Tijdstip > t1.Tijdstip
AND t2.Tijdstip = 0;

Renko op 13-01-2009 om 09:00

Ghehehe, Brenno, heel nice xD.

Rinze op 13-01-2009 om 09:01

Voor MS Access en nu met escaping voor groter dan en kleiner dan tekens (fix je forumcode even!):

SELECT COUNT(*) AS Expr1
FROM transacties AS t1
WHERE t1.Bedrag = -4 AND (
SELECT TOP 1 t2.Bedrag
FROM transacties t2
WHERE t2.Tijdstip > t1.Tijdstip
AND t2.Tijdstip <= DATEADD("n",90,t1.Tijdstip)
) > = 0;

Jeroen Sen op 13-01-2009 om 09:21

Na 10 minuten puzzelen voor MySQL:


– Tabel structuur voor tabel ‘ov_transactie’

CREATE TABLE IF NOT EXISTS ov_transactie (
id int(11) NOT NULL auto_increment,
tijd datetime NOT NULL default ‘0000-00-00 00:00:00′,
kaart_id int(11) unsigned NOT NULL,
locatie_id int(11) unsigned NOT NULL,
poort_id int(11) unsigned NOT NULL,
bedrag decimal(5,2) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;


– Gegevens worden uitgevoerd voor tabel ‘ov_transactie’

INSERT INTO ov_transactie (id, tijd, kaart_id, locatie_id, poort_id, bedrag) VALUES
(1, ‘2009-01-13 00:00:00′, 1, 1, 666, ‘-4.00′),
(2, ‘2009-01-13 00:01:00′, 1, 2, 999, ‘3.30′),
(3, ‘2009-01-13 00:00:00′, 2, 1, 666, ‘-4.00′),
(4, ‘2009-01-13 01:30:01′, 2, 6, 888, ‘2.20′);


– Selecteer de records waarbij de start van de transactie -4.00, geen eindtransactie is binnen anderhalf uur (=5400 seconden)

SELECT
COUNT(*) AS aantal,
(SUM(bedrag) * -1) AS bedrag
FROM
ov_transactie o1
WHERE NOT EXISTS (
SELECT * FROM ov_transactie o2 WHERE o1.kaart_id = o2.kaart_id AND UNIX_TIMESTAMP(o2.tijd) > (UNIX_TIMESTAMP(o1.tijd) + 5400))
AND o1.bedrag = -4.00;

Joris op 13-01-2009 om 09:24

IF admin Fails

Arno op 13-01-2009 om 09:26

Ook is het mogelijk dat sommige mensen wel uitchecken, maar niet inchecken (dat zijn mensen met een abonnement).

Bedoel je het niet andersom Brenno? Met mijn OV jaarkaart check ik bij voorkeur niet uit ( dat kost tijd en privacy ), maar soms ben ik wel genoodzaakt om in te checken. Dat zou onder normale omstandigheden dus 4 euro kosten, maar ik meen dan mijn transacties als 0 euro worden gemarkeerd.

Joris op 13-01-2009 om 09:27

if admin fails
get new admin;
else stop lies

Rinze op 13-01-2009 om 09:40

Ik bedenk me net dat transacties van de laatste anderhalf uur niet meegeteld mogen worden omdat de terugboeking mogelijk nog moet gebeuren, en Jeroen Sen herinnerde me er aan dat ik ook op KaartId moet controleren.

SELECT COUNT(*) AS Expr1
FROM transacties AS t1
WHERE t1.Bedrag = -4
AND Tijdstip <= DATEADD(“n”,-90,NOW())
AND (
SELECT TOP 1 t2.Bedrag
FROM transacties t2
WHERE t2.Tijdstip > t1.Tijdstip
AND t2.Tijdstip <= DATEADD(“n”,90,t1.Tijdstip)
AND t1.KaartId = t2.KaartId
) >= 0;

Hans op 13-01-2009 om 09:41

SELECT *
FROM transacties instap
WHERE Bedrag=-4
AND (SELECT Bedrag
FROM transacties
WHERE instap.KaartID=KaartID
AND Tijdstip=(SELECT MIN(Tijdstip)
FROM transacties
WHERE instap.KaartID=KaartID
AND instap.Tijdstip<Tijdstip
)
)=-4;

hAl op 13-01-2009 om 10:03

Rinzes tweede poging lijkt ergens op.
De rest is rommel

Matthijs op 13-01-2009 om 11:29

Een MySQL ‘oplossing’ m.b.v. een functie die de transactie controleert en gelijk registreert of de transactie goed verlopen is:

Alle uitcheck registraties worden in een temporary table geplaatst, en extra veld ‘checked’ dat in eerste instantie waarde 0
krijgt wordt toegevoegd.


CREATE TEMPORARY TABLE `uitchecken` SELECT * FROM `ov_transactie` WHERE (bedrag>=0);
ALTER TABLE `uitchecken` ADD `checked` INT(11) NOT NULL AFTER `bedrag`;
UPDATE `uitchecken` SET `checked` = 0;

Ook alle incheck registraties worden in een temporary table, en eveneens wordt een extra veld ‘checked’
dat in eerste instantie waarde 0 krijgt toegevoegd.


CREATE TEMPORARY TABLE `inchecken` SELECT * FROM `ov_transactie` WHERE (bedrag=-4);
ALTER TABLE `inchecken` ADD `checked` INT(11) NOT NULL AFTER `bedrag`;
UPDATE `inchecken` SET `checked` = 0;

Een functie wordt gedefinieerd om voor alle incheck registraties te controleren of er een bijbehorende uitcheck registratie is.

Op chronologische volgorde worden nu voor elke incheck registratie de volgende stappen doorlopen:
- Eerst wordt gezocht naar de eerst hierna voorkomende incheck registratie voor betreffende kaart_id,
en deze tijd wordt opgeslagen in @next_incheck.
- Vervolgend wordt gezocht naar de eerst na de incheck voorkomende uitcheck registratie voor betreffende kaart_id met status 0
binnen anderhalf uur na de incheck-tijd. Als bovengenoemd @next_incheck gevonden is (niet null), wordt bovendien geeist dat de
uitcheck voor deze nieuwe incheck-tijd plaats vond.
- Indien er een dergelijke uitcheck gevonden is, wordt de status van deze uitcheck registratie op 1 gezet, en krijgt de
incheck-registratie ook status 1. Op die manier wordt een uitcheck hoogstens geassocieerd met een incheck registratie.
- Indien een dergelijke uitcheck niet gevonden wordt, zal de status van de incheck registratie op 0 gezet worden. Dit zijn dus de
incheck registraties die gezocht worden!


DROP FUNCTION IF EXISTS controleerTransactie;

CREATE FUNCTION controleerTransactie(kaart_id INT(11), tijd DATETIME) RETURNS INT(11)
BEGIN
SET @next_incheck := (SELECT ov_transactie.tijd FROM ov_transactie WHERE (ov_transactie.kaart_id = kaart_id) AND (ov_transactie.bedrag=-4) AND (ov_transactie.tijd > tijd) ORDER BY ov_transactie.tijd ASC LIMIT 0,1);
IF NOT (@next_incheck IS NULL) THEN
SET @uitcheck_id := (SELECT uitchecken.id FROM uitchecken WHERE (uitchecken.kaart_id = kaart_id) AND (uitchecken.checked=0) AND (uitchecken.tijd > tijd) AND (uitchecken.tijd < @next_incheck) AND (uitchecken.tijd tijd) AND (uitchecken.tijd < ADDTIME(tijd,'01:30:00')) ORDER BY uitchecken.tijd ASC LIMIT 0,1);
END IF;
IF NOT (@uitcheck_id IS NULL) THEN
UPDATE `uitchecken` SET `checked` = 1 WHERE `id` = @uitcheck_id;
RETURN 1;
ELSE RETURN 0;
END IF;
END;

De functie wordt toegepast op alle incheck registraties in chronlogische volgorde:


UPDATE `inchecken` SET `checked` = controleerTransactie(inchecken.kaart_id, inchecken.tijd) ORDER BY `inchecken`.tijd ASC;

Hierna kunnen alle incheck registraties waarvan de status niet meer gelijk is aan 0 verwijderd worden.
Voor deze incheck registraties is immers een bijbehorende uitcheck registratie gevonden.


DELETE FROM `inchecken` WHERE NOT (`checked` = 0);

Nu blijven in de (temporary) table ‘inchecken’ alleen die registraties over waar geen passende uitcheck registratie bij gevonden is.
Het aantal rijen in deze table is eenvoudig op te vragen, en eventueel is naar wens ook meer informatie over de betreffende
registraties uit deze table te halen.


SELECT COUNT(*) AS number FROM `inchecken`;

floris op 13-01-2009 om 11:41

Hoe worden abonnementen bijgehouden? dat moet wel bekend zijn. (om een simpel overzicht te genereren.) Er wordt nu data buiten bereik gehouden.

Matthijs op 13-01-2009 om 11:54

Ik heb aangenomen dat mensen met een abonnement nooit inchecken (?), maar alleen (soms) uitchecken. Dan hoef je ook niet te weten welke kaart_id’s een abonnement hebben, hoewel je dat wel kan vinden door alle kaart_id’s van uitcheck-registraties zonder bijbehorende incheck-registratie te zoeken.

MaNo op 13-01-2009 om 12:01

In MySQL:

SELECT *
FROM transactietabel tt1
WHERE tt1.bedrag = -4
AND NOT EXISTS (
SELECT 9
FROM transactietabel tt2
WHERE tt2.Tijdstip = tt1.Tijdstip
AND tt2.Bedrag >= 0
AND tt1.KaartId = tt2.KaartId
);

MaNo op 13-01-2009 om 12:03


SELECT *
FROM transactietabel tt1
WHERE tt1.bedrag = -4
AND NOT EXISTS (
SELECT 9
FROM transactietabel tt2
WHERE tt2.Tijdstip = tt1.Tijdstip
AND tt2.Bedrag >= 0
AND tt1.KaartId = tt2.KaartId
);

MaNo op 13-01-2009 om 12:07

SELECT    *
FROM      transactietabel tt1
WHERE    tt1.bedrag = -4

AND NOT EXISTS (

               SELECT 9

               FROM transactietabel tt2

               WHERE tt2.Tijdstip < ADDTIME(tt1.Tijdstip,
‘1:30:00′)

               AND tt2.Tijdstip >= tt1.Tijdstip

               AND tt2.Bedrag >= 0

               AND tt1.KaartId = tt2.KaartId
);

Tom op 13-01-2009 om 13:34

Rinzes tweede poging lijkt ergens op.
De rest is rommel

Misschien even een update?

Of nog leuker, zelf iets verzinnen

hAl op 13-01-2009 om 14:15

@Tom,
Ik ken geen access SQL en mijn mysql kennis is te roestig om zonder beschikbare testdatabase er iets mee te kunnen (een valide query maken).

Wel zou ik zeggen dat voor de mooiere versie het gebruik van een een VervoersbedrijfID (zou moeten bestaan blijkens het voorbeeld van Brenno) in de query op te nemen en de aantalllen te groeperen op per vervoersbedrijf en de subselect ook te matchen op VervoersbedrijfID (je checked namelijk altijd uit bij hetzelfde vervoersbedrijf).

Er blijft echter in al bovenstaande queries nog een (beperkte) onzekerheidsfactor in het resultaat hangen.
Als je eerst een ritje 4 euro heb betaald en niet ben uitgecheckt maar je stapt bijvoorbeeld een uur later weer in dezelfde lijn terug (met dus opnieuw een 4 euro checkin) en checkt dan vervolgens wel tijdig uit dan registreren al de bovenstaande queries de eerste verloren vier euro niet meer.

Dat ondervangen is ook wel prima oplosbaar maar het zou zonde zijn als ik dat nu even hier neerpende hoe. Misschien is dat beter voor de liefhebber voor wie het nog een beetje een uitdaging is om SQL queries te maken.

Matthijs op 13-01-2009 om 14:20

@hAl

Volgens mij registreert mijn oplossing dat wel; ik zoek naar de eerstvolgende incheck en als die op een tijdstip voor de eerstvolgende uitcheck plaatsvindt, wordt de incheck als ‘verloren’ geregistreerd

hAl op 13-01-2009 om 14:28

Sorry Matthijs,
Maar jouw inzending heb ik niet bekeken omdat die te veel afweek van de vraagstelling (een query op basis van een bestaande tabel) en ook omdat het me hier te veel tijd zou kosten om je code plus uitleg te doorploegen.
Mijn excuses.

(p.s. Wat is eignelijk het omgekeerde van boven???)

Arno op 13-01-2009 om 14:40

Ik zat even te gedachtenexperimenteren en kom tot de volgende vraag. Hoe zit het als je een reis doet van precies 8 euro ?
Dan check je in , dat kost -4 euro. Later check je uit en moet je nog eens -4 euro betalen. Dan lijkt die uitcheck op een incheck, en dan lijkt het dus of je 2 maal een uitcheck mist. Het zou dus slim zijn als de tabel ook een vlaggetje zou hebben dat aanheeft of het om een incheck of uitcheck gaat..

Matthijs op 13-01-2009 om 14:41

Ah, inderdaad vergeten erbij te zetten: ik ben uitgegaan van de table zoals Jeroen Sen die in z’n post noemde. En het is idd een hele lap code, maar toch niet veel meer dan een grote query op de gegeven table.

(het omgekeerde van rechts… ook al zo lastig ;-) )

Tim op 13-01-2009 om 17:53

Net als Arno vraag ik me ook af hoe dat precies zit, zijn uitcheck bedragen altijd groter dan 0? Dat zou impliceren dat er geen reizen zijn duurder dan 4 euro… Als ook uitcheckbedragen negatief kunnen zijn zou er een in/uitcheckflag moeten zijn lijkt mij, anders is er geen (correcte) oplossing voor het gestelde probleem (toch?).

Lex op 13-01-2009 om 18:34

Het zijn toch geen tijdreizen? Geen idee hoe ze het ingericht hebben maar ik introduceer afstand.

Twee hulptabellen, locaties:

locatie_id locatie
1 CS
4 Wibautstraat
5 Weesperplein

En een afstanden-matrix met locatie-id “0″ als strafcorner:

van naar km euro
1 4 6 1,20
1 5 8 1,60
4 5 2 0,40
1 0 20 4,00
4 0 20 4,00
5 0 20 4,00

Tabel met transacties:

id tijd kaart_id locatie_id
1 13-1-2009 11:00:00 1 1
2 13-1-2009 11:01:00 1 5
3 13-1-2009 11:00:00 2 1
4 13-1-2009 12:30:00 2 4
5 13-1-2009 11:00:00 3 1

Niet uitputtend maar ja, ik werk daar niet:

SELECT transacties.kaart_id,
Min(transacties.locatie_id) AS van,
IIf(Max([locatie_id])=Min([locatie_id]),0,Max([locatie_id])) AS naar
FROM transacties
GROUP BY transacties.kaart_id;

Levert “van_naar”:
kaart_id van naar
1 1 5
2 1 4
3 1 0

Op basis van “van_naar” naar een overzicht:

SELECT van_naar.kaart_id,
Val(Nz([km],10)) AS afstand,
afstanden.euro
FROM afstanden
INNER JOIN van_naar ON (afstanden.naar = van_naar.naar)
AND (afstanden.van = van_naar.van);

Overzicht afgelegde afstand en ritprijs:

kaart_id afstand euro
1 8 1,60
2 6 1,20
3 20 4,00

Lex op 13-01-2009 om 20:02

Nog even, RET weet het nu, Teletext 13-1-08, 18:00:

Zeker 220.000 reizigers in Rotterdam
zijn het afgelopen jaar vergeten uit te
checken met hun ov-chipkaart.De helft
daarvan heeft bij vervoerder RET geld
teruggevraagd,blijkt uit cijfers van de
RET.Het bedrijf zegt dat zij hun geld
hebben teruggekregen,gemiddeld 2 euro.

Moest koken, maar tijdens de bereiding bedacht ik hoe het iets sjieker kon (sql-culinair, heet dat):

Afstanden-matrix nu:
van naar km euro
1 4 6 1,20
1 5 8 1,60
4 5 2 0,40
1 1 0 0,00
4 4 0 0.00
5 5 0 0,00

Aan transacties nog een “case” toegevoegd:
id tijd kaart_id locatie_id
1 13-1-2009 11:00:00 1 1
2 13-1-2009 11:01:00 1 5
3 13-1-2009 11:00:00 2 1
4 13-1-2009 12:30:00 2 4
5 13-1-2009 11:00:00 3 1
6 13-1-2009 11:00:00 4 1
7 13-1-2009 11:05:00 4 1

Productie van_naar:
SELECT transacties.kaart_id,
Min(transacties.locatie_id) AS van,
Max(transacties.locatie_id) AS naar,
Count(transacties.kaart_id) AS lezer
FROM transacties
GROUP BY transacties.kaart_id;

Geeft:
kaart_id van naar lezer
1 1 5 2
2 1 4 2
3 1 1 1
4 1 1 2

Voor het overzicht:
SELECT van_naar.kaart_id,
afstanden.km AS afstand,
lezer Mod 2-1 AS checkout,
IIf(checkout,euro,4) AS ritprijs
FROM afstanden
INNER JOIN van_naar
ON (afstanden.naar = van_naar.naar)
AND (afstanden.van = van_naar.van);

Overzicht:
kaart_id afstand checkout ritprijs
1 8 -1 1,60
2 6 -1 1,20
3 0 0 4,00
4 0 -1 0,00

Degene op het CS die zich bedacht krijgt niets in rekening gebracht.

hAl op 13-01-2009 om 23:39

@Tim
Er is dus mogelijk ook geen 100% oplossing op basis van het geschetse scenario.
Dat is echter ook niet nodig.
Een 99% oplosssing voldoet indien je maar beseft waar de zwakheid in de query zit en inziet dat dat geen significant effe3ct op het resultaat heeft.

hAl op 14-01-2009 om 09:47

Nu de prijzen voor bovenstaande zijn toegekend (iedereen hierboven met een scriptje wint wint volgens Brenno !!) zou ik zelf het waarschijnlijk op simpel gedaan hebben.
Bijvoorbeeld:
SELECT bedrag, “;”, Count(*)
INTO OUTFILE ‘bedragoverzicht.csv’
FIELDS TERMINATED BY ‘;’
LINES TERMINATED BY ‘\n’
FROM transactietabel
GROUP BY bedrag

Het geeft je niet direct een antwoord maar je kunt dan het resultaat bestand (1 regel met alle incheckbedragen van -4,00 en per elk ander uitcheckbedrag een regel) direct in excel kwakken en er allerlei conclusies uithalen (en er desnoods nog later allerlei extra interesante statistiekjes uit proberen te halen die je niet meteen in je originele query had meegenomen. (bijvoorbeeld de gemiddelde kosten van een ritje zodat je daadwerkelijk het gemiddeld ‘verlies’ van de reiziger zou kunnen bepalen.)
Indien er ook nog een VervoersbedrijfID in de transactietabel zou zitten kunnen die nog toevoegen in de select en grouped by clauses om je statjes per vervoersbedrijf te kunne vergelijken.

Door te precies naar een bepaald resultaat te queryen verlies je vaak informatie waar je nog wat mee zou kunnen of die de informatie in een beter perspectief zou plaatsen.

COPE op 14-01-2009 om 10:14

Ons bedrijf specialiseert in dit soort vraagstukken. Met behulp van krachtige ETL tools (wij gebruiken de uiterst snelle en flexibele Diver Solution van Dimensional Insight) kun je dit soort gegevens inzichtelijk maken.

Wij doen dat zeer regelmatig, ook voor dit 4 euro-probleem bij de OV chipkaart zou dat voor ons zeer waarschijnlijk makkelijk op te lossen zijn.

Zonder verdere ingewikkelde SQL queries.

Geen script dus van mij, maar wel de aanbieding dat, indien mogelijk, ik met behulp van een dump van alle transacties dit probleem zeer waarschijnlijk in een paar dagen opgelost zou hebben.

Abx op 14-01-2009 om 13:25

Er zijn nog een aantal zaken die onduidelijk zijn:

1. Er wordt gesproken over een ID voor de vervoerder, deze staat echter niet in de voorgestelde tabel. (zit deze er nu wel of niet in?)

2. Zoals eerder genoemd is het de vraag of elke transactie van -4 een starttransactie is, of dat er bij een ritprijs van 8 euro 2x -4 wordt gerekend voor dezelfde prijs.

3. Zijn de id’s voor poortjes bij een metro bij binnenkomst gelijk of anders dan hetzelfde poortje maar dan voor uitgaan.

4. Als iemand incheckt met abbonnement wordt dat nooit geregistreerd, of is er wel een registratie.

5. Als iemand uitcheckt met abbonement, is dat een betaalde rit die alleen niet wordt afgerekend (kan dit ook toevallig -4 zijn dan) of is dit een 0 transactie.

Misschien zijn er nog meer vragen, maar deze vielen mij direct op.

Abx op 14-01-2009 om 13:30

Bij punt 2 moet ‘dezelfde prijs’ vervangen worden met ‘de genoemde ritprijs’.

Als dit echt de tabellen zijn waarop alles gebaseerd is, dan kunnen de vervoerders nog een hoop problemen verwachten en is het systeem nog veel en veel slechter als ik had gedacht.

hAl op 14-01-2009 om 13:44

[

2. Zoals eerder genoemd is het de vraag of elke transactie van -4 een starttransactie is, of dat er bij een ritprijs van 8 euro 2x -4 wordt gerekend voor dezelfde prijs.

Een prijs van 8 euro vereist momenteel een reis bij een stadsvervoerder van 72,5 kilometer tussen in en uitcheck halte. Dat gaat je momenteel zeker nog niet lukken.
Pas als de NS meedoet kun je dergelijke checkin bedragen redelijkwijs gaan verwachten maar is het nog maar de vraag of de tariefstructuur die de NS hanteert een dergelijk exact bedrag van 8 euro mogelijk maakt.

Wim Zoet op 14-01-2009 om 15:58

De truc voor het goed kunnen selecteren op ontbrekende records is het kunnen uitzoeken of een record mist in de transactielijst van een kaart wanneer we die sorteren op tijd.

Omdat de records verspreid binnen kunnen komen, kunnen ze id’s hebben die niet op volgorde staan, dus die kunnen we niet gebruiken.

Ter test maken we de tabel aan en vullen we deze.
NB. Onderstaande SQL code is voor SQL server, een vergelijkbare tabelstructuur kan in access ook aangemaakt worden.

CREATE TABLE OVtransactie (
transactieId int NOT NULL identity(1,1),
tijdstip datetime NOT NULL default current_timestamp,
kaartId int NOT NULL,
locatieId int NOT NULL,
poortjesId int NOT NULL,
bedrag decimal(5,2) NOT NULL,
PRIMARY KEY (transactieId)
) ;

INSERT INTO OVtransactie ( tijdstip, kaartId, locatieId, poortjesId, bedrag) VALUES
( ‘2009-01-13 00:00:00′, 1, 1, 666, -4.00),
( ‘2009-01-13 00:01:00′, 1, 2, 999, 3.30), — Geldige checkout
( ‘2009-01-13 00:00:00′, 2, 1, 666, -4.00),
( ‘2009-01-13 01:30:01′, 2, 6, 888, 2.20),
( ‘2009-01-13 02:00:00′, 1, 1, 666, -4.00), — Ongeldige checkout
( ‘2009-01-13 02:30:00′, 1, 1, 666, -4.00); — Checkin die meer dan 1,5u terug is

Om de ongeldige records op te vragen kan de volgende query worden gebruikt:

select *
from OVtransactie ov1
where bedrag=-4
and not exists (
select transactieId
from OVtransactie ov2
where bedrag>=0
and kaartId=ov1.kaartId
and tijdstip > ov1.tijdstip
and ((tijdstip ov1.tijdstip
and kaartId=ov2.kaartId)
) or not exists (select transactieId
from OVtransactie ov4
where tijdstip>ov2.tijdstip
and kaartId=ov2.kaartId
))
)
and (exists (
select transactieId
from OVtransactie ov5
where bedrag=-4
and kaartId=ov1.kaartId
and tijdstip>ov1.tijdstip
) or
CURRENT_TIMESTAMP>DATEADD(n,90,ov1.tijdstip)
)

Om de query in access te laten werken, dient CURRENT_TIMESTAMP te worden vervangen door Now() en de n in de DataAdd functie dient tussen quotes te worden gezet.

De query selecteert eerst alle checkin records en kijkt per record of er checkout records zijn met een later tijdstip. Dit tijdstip dient te liggen tussen het tijdstip van checkin en het tijdstip van een checkin die later is dan het tijdstip van het huidige record.

Om er zeker van te zijn dat we niet de laatste checkout meetellen, controleren we of er nog nieuwe checkin records zijn.

Als laatst wordt gecontroleerd of het huidige checkin record niet ouder is dan 1,5u, dat wordt niet door de andere condities meegenomen.
Van tussenliggende records wordt de tijd tussen in en uitchecken niet berekend. Die 1,5 uur is ook maar een getal en met de weersomstandigheden van de laatste weken zou dit maar zo eens een kwartiertje langer kunnen worden.

De query retourneert de checkin records de geen checkout record hebben.
Er wordt niet gekeken naar records voor abonnementen. Omdat een volledig record wordt teruggegeven, kan het prima als input dienen voor een willekeurig rapport met aantallen in totaal, aantallen per kaarthouder, aantallen per kaartsoort (persoonlijk of anomieme kaart) etc.

Opmerkingen en suggesties zijn altijd welkom

Lex op 14-01-2009 om 18:18

De wedstrijd is gesloten maar anders zou het ontbreken van een “vervoerders_id” zich nu extra manifesteren…

Teletekst wo 14 jan:
De Spoorwegen willen voor het gebruik
van de ov-chipkaart een instaptarief
van 20 euro hanteren.Dat wordt als borg
afgeschreven bij het instappen.Na de
reis moet de reiziger uitchecken met de
ov-chipkaart,waarbij de borg wordt
verrekend met de afgelegde kilometers.

NS gaat in mei met de ov-chipkaart
proefdraaien op de Schiphollijn.Over
het instaptarief wordt nog overlegd.

Tom op 15-01-2009 om 09:17

“Het geeft je niet direct een antwoord maar je kunt dan het resultaat bestand (1 regel met alle incheckbedragen van -4,00 en per elk ander uitcheckbedrag een regel) direct in excel kwakken en er allerlei conclusies uithalen”

@hAl, je levert geen oplossing maar je verschuift het probleem naar een volgend platform.
Dat had handiger gekund, want zoals ervaren Excel-ridders wel weten hoef je geen CSV-tussenbestand aan te maken, maar kan Excel direct data uit een database laden.
Maar het blijft dat het geen oplossing is.

“(en er desnoods nog later allerlei extra interesante statistiekjes uit proberen te halen die je niet meteen in je originele query had meegenomen. (bijvoorbeeld de gemiddelde kosten van een ritje zodat je daadwerkelijk het gemiddeld ‘verlies’ van de reiziger zou kunnen bepalen.)”

Voor dit doel is SPSS natuurlijk onovertroffen. Overigens kan SPSS ook direct op een database aansluiten

Reageer

Plaats een reactie
Naam (verplicht)
E-mail (verplicht, niet zichtbaar bij reactie)
Website (http://www.mijnwebsite.nl)

Wat is het omgekeerde van rechts?

XHTML: Je kunt gebruik maken van deze tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Verleden nieuws