Webbutvecklingsdagbok (Webbutveckling, 120 hp)

Permalänk
Medlem

Blir på riktigt orolig, nej livrädd, att efter flera års utbildning inte ens skapa relativt säkra formulär

Man kan ibland undra vad ni lärt er

Permalänk
Skrivet av medbor:

Blir på riktigt orolig, nej livrädd, att efter flera års utbildning inte ens skapa relativt säkra formulär

Man kan ibland undra vad ni lärt er

Ja, det är minst sagt pinsamt! Nämnde jag att vi har i vissa projekt använt oss av localStorage-tokens för att genomföra anrop inuti SPA? I MERN-stacks-kursen implementerade jag dock access & refresh tokens via httpOnly och inga localStorage-fuffens.

Fast det är mest jag som glömt basic stuff i vissa IActionResult-metoder vilket jag fixar nu - efter mycket uppskattad pen-test från erfaren programmerings-Discord-person - även fast jag redan lämnat in arbetet för slutgiltigt betygsättning. Det hela kan alltså kanske säga mycket mer om mig än själva utbildningen. Det får folk som kikar på mina tidigare kodprojekt avgöra.

Vad jag kan säga är att SecDev-delen har varit i denna Webbutvecklingsutbildning på distans att förhindra SQL-injektioner, XSS, CSRF och även övrig sanering av data utifrån kravspecifikation (t.ex. sanera bort (vissa) HTML-taggar om det inte ska få lagras). Också ett slags SecDev-översiktstänk har varit bristande såsom att exempelvis bestämma i förväg "Principle of Least Privilege" och skapa en pipeline av säkerhetskontroller i t.ex. PHP eller NodeJS som används konsekvent. MAO & TLDR: Noll arkitekturmässigt säkerhetstänk här mer än det redan ovannämnda.

En liten detalj jag fastnat på nu är beträffande id:n som skickas med från formulär. Anta att jag öppnar följande som verifierar så klart att jag äger annonsen:

http://localhost:5235/mina-annonser/7/redigera

Men hyptotetiskt skulle jag också kunna äga annons 8 och då kan jag ändra detta direkt i HTML-filen innan jag skickar iväg:

// .cshtml-fil <input type="hidden" asp-for="Id" /> // genererad HTML <input type="hidden" data-val="true" data-val-required="The Id field is required." id="Id" name="Id" value="7"> // ändrar ren HTML <input type="hidden" data-val="true" data-val-required="The Id field is required." id="Id" name="Id" value="8">

Så jag skulle då ha ändrat min annons dolda id-värde (value="7" till value="8") från formuläret för min annons 7 då ägandekontrollen skulle gå igenom. En lösning kanske vore att tillfälligt lagra något i databasen när jag kommer till en redigeringssida som det kan jämföras mot när jag skickar iväg och så då kan den neka att jag skickar uppdatering för annons 8 fast jag äger den.

Utan då måste jag klicka på Redigera-knappen för annons 8 eller besöka /mina-annonser/8/redigera vilket då uppdaterar i databasen igen för förberedelse att nu kan en uppdatering för annons 8 komma - självfallet kontrolleras ägandeskap precis som tidigare.

Jag kan knappast vara den första att ha tänkt ut lösningar på detta uppenbara formulärproblem? Så klart kan man ställa sig frågan varför någon som äger två annonser skulle försöka ändra sin andra annons genom att redigera ett dolt id-värde för sin nuvarande annons? 🤔

Mvh,
WKL.

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Ja, det är minst sagt pinsamt! Nämnde jag att vi har i vissa projekt använt oss av localStorage-tokens för att genomföra anrop inuti SPA? I MERN-stacks-kursen implementerade jag dock access & refresh tokens via httpOnly och inga localStorage-fuffens.

Fast det är mest jag som glömt basic stuff i vissa IActionResult-metoder vilket jag fixar nu - efter mycket uppskattad pen-test från erfaren programmerings-Discord-person - även fast jag redan lämnat in arbetet för slutgiltigt betygsättning. Det hela kan alltså kanske säga mycket mer om mig än själva utbildningen. Det får folk som kikar på mina tidigare kodprojekt avgöra.

Vad jag kan säga är att SecDev-delen har varit i denna Webbutvecklingsutbildning på distans att förhindra SQL-injektioner, XSS, CSRF och även övrig sanering av data utifrån kravspecifikation (t.ex. sanera bort (vissa) HTML-taggar om det inte ska få lagras). Också ett slags SecDev-översiktstänk har varit bristande såsom att exempelvis bestämma i förväg "Principle of Least Privilege" och skapa en pipeline av säkerhetskontroller i t.ex. PHP eller NodeJS som används konsekvent. MAO & TLDR: Noll arkitekturmässigt säkerhetstänk här mer än det redan ovannämnda.

En liten detalj jag fastnat på nu är beträffande id:n som skickas med från formulär. Anta att jag öppnar följande som verifierar så klart att jag äger annonsen:

http://localhost:5235/mina-annonser/7/redigera

Men hyptotetiskt skulle jag också kunna äga annons 8 och då kan jag ändra detta direkt i HTML-filen innan jag skickar iväg:

// .cshtml-fil <input type="hidden" asp-for="Id" /> // genererad HTML <input type="hidden" data-val="true" data-val-required="The Id field is required." id="Id" name="Id" value="7"> // ändrar ren HTML <input type="hidden" data-val="true" data-val-required="The Id field is required." id="Id" name="Id" value="8">

Så jag skulle då ha ändrat min annons dolda id-värde (value="7" till value="8") från formuläret för min annons 7 då ägandekontrollen skulle gå igenom. En lösning kanske vore att tillfälligt lagra något i databasen när jag kommer till en redigeringssida som det kan jämföras mot när jag skickar iväg och så då kan den neka att jag skickar uppdatering för annons 8 fast jag äger den.

Utan då måste jag klicka på Redigera-knappen för annons 8 eller besöka /mina-annonser/8/redigera vilket då uppdaterar i databasen igen för förberedelse att nu kan en uppdatering för annons 8 komma - självfallet kontrolleras ägandeskap precis som tidigare.

Jag kan knappast vara den första att ha tänkt ut lösningar på detta uppenbara formulärproblem? Så klart kan man ställa sig frågan varför någon som äger två annonser skulle försöka ändra sin andra annons genom att redigera ett dolt id-värde för sin nuvarande annons? 🤔

Mvh,
WKL.

Som du i alla fall upptäckt så är ju inte dolda värden dolda på riktigt. Man skulle kunna ge ut edit-token för enskilda annonser så man vet och kan validera vilken den hör till. Men också att validera användaren vid varje formulär/anrop så man vet att personen har rätt rättigheter att utföra ändringen

Permalänk
Skrivet av medbor:

Som du i alla fall upptäckt så är ju inte dolda värden dolda på riktigt. Man skulle kunna ge ut edit-token för enskilda annonser så man vet och kan validera vilken den hör till. Men också att validera användaren vid varje formulär/anrop så man vet att personen har rätt rättigheter att utföra ändringen

Jag har ju så här just nu i C#.NET ASP.NET Core MVC-kod beträffande Redigeringssidan:

// GET: Ads/Edit/5 [Authorize] // Must be logged in [Route("/mina-annonser/{id:int}/redigera")] public async Task<IActionResult> Edit(int? id)

Sen hämtar jag vald annons och kontrollerar att det är rätt ägare bakom den:

// Get the ad+images by id var ad = await _context.Ads .Include(a => a.AdImages) .FirstOrDefaultAsync(m => m.Id == id); // Check if the ad's UserId matches the UserId of the current user if (ad.UserId != currentUser.Id) { // Show default page when denied ViewData["ErrorType"] = "Åtkomst nekad!"; ViewData["ErrorMessage"] = "Du saknar behörighet att redigera denna annons!"; return View("DeniedOrNotFound"); }

För att POSTa redigerad annons uppgifter (ej bilder) så har jag först:

// POST: annonser/5/redigera [Authorize] [HttpPost] [ValidateAntiForgeryToken] // Check correct user made the request public async Task<IActionResult> EditSave(int id, [Bind("Id,Title,Description,Category,Price")] Ad ad)

Utöver CSRF-skyddet så är det denna kontroll som gör det sista jag kan göra:

// Load existing ad and current user var existingAd = await _context.Ads .Include(a => a.AdImages) .FirstOrDefaultAsync(a => a.Id == id); var currentUser = await _userManager.GetUserAsync(User); // Check if the ad exists and if the logged-in user owns it if (existingAd == null || existingAd.AdByUsername != currentUser.UsernameShown) { ViewData["ErrorType"] = "Hacker upptäckt!"; ViewData["ErrorMessage"] = "Du håller på med databashacking. Inte coolt!"; return View("DeniedOrNotFound"); }

Utöver Edit-tokens, äré inte CSRF-skyddet du syftade på?

Mvh,
WKL.

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk

Jag vill tacka så mycket för "den välmenade utskällningen" här och på Discord!

Jag har nu åtgärdat majoriteten av sårbarheterna såväl som tillfört en extra modell bara för träningens skull (en dag får jag nog betalt för det) vilket implementerat "MVP" av tokens för varje undersida som stödjer CRUD:

CRUDToken datamodell:

using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using projekt; namespace projekt; // CRUDToken model - Used to associate a token with a user and an ad for // making sure that the user can only edit their ads and that they cannot // change the ad Id in order to change either their own or someone else's ad. // They can also then only edit one of their ads at a time. It can also be done // To make sure a user can only CRUD their own ads and images! public class CRUDToken { public int Id { get; set; } // Foreign key (UserId) for ApplicationUser (User) public string? UserId { get; set; } [ForeignKey("UserId")] public ApplicationUser? User { get; set; } // Foreign key (AdId) for Ad public int? AdId { get; set; } [ForeignKey("AdId")] public Ad? Ad { get; set; } // Token for CRUD ads and/or images public string? Token { get; set; } }

Dold text

Kod i kontrollerfilerna:

// AddToken adds a token associated with current UserId and AdId and sends it to the user private string AddToken(int? adId, string? UserId) { // Remove all current tokens for current user's adId // This makes it so that if the user does open a new tab it will not be able to use the old token var tokens = _context.CRUDTokens.Where(t=> t.UserId == UserId); if(tokens != null){ _context.CRUDTokens.RemoveRange(tokens); _context.SaveChanges(); } // Then prepare a new CRUDToken with unique token (GUID) var token = new CRUDToken(); token.AdId = adId; token.UserId = UserId; token.Token = Guid.NewGuid().ToString(); // Add the token to the CRUDTokens table _context.CRUDTokens.Add(token); _context.SaveChanges(); // Return the newly generated token return token.Token; } // CheckToken checks if Current UserId has a token associated with AdId private bool CheckToken(int? adId, string? UserId, string? token) { return _context.CRUDTokens .Any(t => t.AdId == adId && t.UserId == UserId && t.Token == token); } // Remove one token associated with current UserId and AdId // This is used after a successful POST action has been performed private void RemoveToken(int? adId, string? UserId, string? token) { var removeToken = _context.CRUDTokens .FirstOrDefault(t => t.AdId == adId && t.UserId == UserId && t.Token == token); if(removeToken != null){ _context.CRUDTokens.Remove(removeToken); _context.SaveChanges(); } }

Dold text

Flödet är att varje gång du besöker en sida där du kan skapa, ändra och/eller radera utifrån givet id så skapas en GUID-genererad token som ligger "dolt" i ett inmatningsfält. Detta lagras också i databasen för nuvarande användare medan deras tidigare besök för andra CUD-sidor raderas ur samma databastabell.

Således kan du inte öppna flera flikar i webbläsaren för varje ny flik kommer att radera tidigare CRUDToken ur databastabellen. Efter lyckat CUD-anrop (vilket då gjort något i databasen för den verifierade användaren) så raderas samma CRUDToken för den användaren.

Sårbarheten som finns här är om du skulle få tag på CRUDToken och en användare har lämnat sidan men inte öppnat någon annan sida som genererar en ny CRUDToken och så skickar du rätt mot rätt API-ändpunkt. Så sista som fattas är väl datum som kan löpa ut? Detta kan dock väcka missnöje då det i princip skapar en "nedräkning" för hur fort användaren måste genomföra en viss CUD.

Men det var en bra ytterligare SecDev-grej att göra. Kan det betraktas som en variant av CSRF i och med att i denna modell+kontroller kontrollerar vi mot en användares sammankopplade annonser?

Mvh,
WKL.

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk

Ensam kodare är okunnig

Rubriken ovan syftar på att jag är utan teknisk handledare i mitt pågående exjobb och jag har redan stött på ett "otrevligt" problem med "vanilj SQL"-kodande. Nej, 🤖chatGPT🤖 har inte visat sig vara så hjälpsam när väl kommer till komplex SQL som inte heller verkar finnas på YouTube. Men, hur har det gått med de två övriga kurserna innan den numera pågående exjobb-kursen? 🤔

Webbutveckling med .NET har fått mig att respektera EF Core
Jag fick slutbetyg i kursen Webbutveckling med .NET och användningen av EF Core och snarare avsaknaden av den nu när jag är i fullrulle med exjobbet där jag använder MariaDB, vanilj SQL- & PHP-kod har fått mig att respektera den bekväma kraften med EF Core särskilt när du vill hantera 10 olika tabeller samtidigt bara för att din tidigare databaslärare ska ge tummen upp för all normalisering.

Det är helt otroligt att EF Core kan inkludera flera SQL-tabeller och sedan kan jag filtrera ut vad jag ska ha och magiskt kan den med sina "navigation properties" slå ihop iterationsbara data att presenteras för besökaren på webbplatsen. Nu när jag fått slutbetyget så finns min webbplats hos Microsoft Azure ej kvar längre. Den varade bara ett par dagar för lärarna var snabba på att rätta.

Väntar fortfarande på Affärsplaner & kommersialisering...
Den andra kursen så har läraren (under Deltagare) inte varit inne på över två veckor nu (15 dagar i skrivande stund). Möjligen kommer läraren att bara lägga upp allt direkt i Ladok så där kommer en då att få se slutgiltigt betyg. Jag har inte sett något ännu och inte hört från någon annan i klassen om att ha fått någonting rättat. Lärarna ska om jag minns rätt ha runt tre veckor på sig att hinna rätta. Ingen i klassen verkar ha fått något rättat och då har vissa lämnat in tidigare än andra.

L(/P-)oopia.se - Webbhotellet med dyra "domäntjänster" och idiotiska VPS-förslag
Vidare till exjobbet där jag - like a digital clout chaser - är utan någon teknisk handledare så när jag kör fast så måste jag inte bara besluta huruvida jag ska plöja igenom det jag fastnat i eller om jag ska välja en "snabbare" och därmed kanske "enklare" väg.

Samtidigt måste jag också meddela uppdateringar till exjobb-givaren vilket kanske kan väcka missnöje eller funderingar om hur "kompetent" jag egentligen är baserade på mina tidigare skrytsamma slutbetyg i tidigare kurser. Det är som om jag egentligen bara är duktig på att lösa skoluppgifter men inte faktiska verkliga problem.

Exjobb-givaren har Loopia - eller "Poopia" som jag numera kallar det för - som "sjösättningslösning". Det går inte att fixa Laravel där utan krångel eller utan riktig VPS vilket kostar 280 + moms per månad vilket jag tvekar starkt på att exjobb-givaren vill investera i. Exjobb-givare vill ju ha våra gratistjänster inte information om att de måste investera i bättre "sjösättningsteknologi" (eng. deployment technology)!

Poopia har också dyra domäntjänster (t.ex. konstanta DNS-avgifter vilket verkar ingå hos exempelvis Inleed - ej sponsrad!) så inte gör det saken bättre. Sedan läste jag på om deras VPS-förslag där de då föreslår att du köper VPS för att sedan installera WordPress på din VPS!? 🙃

Det var också lite krångel i början att få tillgång till phpMyAdmin-konto och enskilt databaskonto bara för att kunna ansluta till databasen. I mitt databaskonto vid universitetet så kunde jag använda samma konto för att komma in på phpMyAdmin såväl som att ansluta mot databasen. Att skilja på dessa är såklart bättre för då kan man radera behörigheter som DROP direkt för databasanvändaren och endast kan tabeller raderas genom att komma in på phpMyAdmin-kontot. Så inga klagomål där.

Vad i MariaDB/SQL är det då jag bråkar med?
För närvarande har jag 10 tabeller där två tabeller har med användare att göra med så de är inga större problem. Utmaningen kommer till tabeller med relationer där de också har olika mängder data. Nedanför har jag "fördunklat" vad de faktiska entiteterna heter och syftar på men deras relationsmässiga motsvarigheter överensstämmer rätt bra:

-- Every Car CREATE TABLE Cars ( id INT PRIMARY KEY AUTO_INCREMENT, car_description VARCHAR(500) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, sold_at TIMESTAMP DEFAULT NULL ); -- Every Car Has A number of Tires CREATE TABLE Tires ( id INT PRIMARY KEY AUTO_INCREMENT, car_id INT NOT NULL, tire_pressure INT NOT NULL CHECK (point BETWEEN -10 AND 10), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, filled_up_at TIMESTAMP DEFAULT NULL, FOREIGN KEY (car_id) REFERENCES Cars(id) ON DELETE CASCADE ); -- Categories to categorize each Car CREATE TABLE Categories ( id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(128) NOT NULL UNIQUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- Attach several categories to the same Car CREATE TABLE CategoriesCars ( car_id INT, category_id INT, category_value VARCHAR(128) NOT NULL, FOREIGN KEY (car_id) REFERENCES Cars(id) ON DELETE CASCADE, FOREIGN KEY (category_id) REFERENCES Categories(id) ON DELETE CASCADE, PRIMARY KEY (car_id, category_id) );

Det är när jag vill SELECTa för att sedan kunna hämta alla bilar där varje bil sedan har alla antal däck kopplade till sig och sedan även har varje bil alla sina kopplade tilldelade kategorier. Men data som skapas i SQL är såklart massor av tomma fält med NULL och den kan till och med utesluta vissa rader med annars tillgänglig data för att den kanske inte fann någon kategori för en bil eller inga däck till en given bil fast bilen annars finns!

Detta är inget problem när EF Core magiskt med sina "navigation properties" definierade för alla databastabeller kan slås samman och sedan returnera klassisk JSON-aktig data (jag vet, det är inte riktigt det i C#.NET) utan att det skulle komma med massa NULL-värden här och var eller ännu värre: massa dubbletter.

Visst skulle jag kunna rekommendera exjobb-givaren att investera i Loopia VPS så jag får slänga upp C#.NET (ironiskt så kom strikt typat till undsättning trots att jag avskytt det under hela C#-kurserna fast kanske det är EF Core jag älskar?) så kommer det gå mycket smidigare med all SQL:ande.

Jag förstår inte hur riktiga systemdatabasadministratörer lyckas slå samman flera tabeller utan massor av dubbletter och/eller NULL-värden pga. olika antal kolumner i tabeller. Och hur gör då EF Core det? Den kan ju inte bara göra en supersmart query utan också någon rejäl databearbetning som på något vis verkar vara snabbare än hur kanske Eloquent i Laravel gör det?

Ja, hade Laravel gått att slänga upp så hade ju kanske Eloquent också löst en hel del. Det finns något som heter Medoo.in men det verkar dock inte erbjuda den där smarta bearbetningen av data som jag behöver utan underlättar bara queryn.

Vad har jag då för "ensam kodare är okunnig"-lösning då med MariaDB/SQL?
Den "gymnasieprogrammerar"-lösningen jag funderar då på är att nyttja mina styrkor: hantera JSON. Det går nämligen att - troligen högst prestandaineffektivt så det bara sjunger om det - hämta ut SQL-data som JSON_OBJECT som jag sedan kan bearbeta i renodlad PHP.

SELECT JSON_OBJECT( 'CarID', Cars.id, 'Description', Cars.car_description, 'CreatedAt', Cars.created_at ) AS json_cars, JSON_ARRAYAGG( JSON_OBJECT( 'CarID', Tires.car_id, 'TireID', Tires.id, 'Pressure', Tires.tire_pressure, 'CreatedAt', Tires.created_at ) ) AS json_tires FROM Cars LEFT JOIN Tires ON Cars.id = Tires.car_id GROUP BY Cars.id;

Koden ovan ger mig då två objekt att bearbeta där jag slår ihop alla däck med rätt CarID med rätt Cars.id. Men då kommer nästa krux: hur sjutton gör jag med alla kategorier som varje bil också har tilldelad? Då måste jag hämta alla kategorier ur Categories-entiteten och slå den samman med CategoriesCars och sen få in denna för varje rätt Cars.id.

Frågan är också då: Är det detta EF Core egentligen gör under ytan? Hur kan den annars skapa JSON-aktig data som inte innehåller dubbletter och/eller NULL-värden på grund av eventuella JOIN-klausuler? En idé jag har är att slänga in alla tabeller i ett C#.NET EF Core projekt och sedan försöka utläsa vad för SQL som körs i Terminalen och se om det går att anpassa samma SQL-körning i MariaDB. Men jag misstänker starkt att mer genomförs med den erhållna SQL-datan och inte bara en "magisk" SQL Query och vips så har vi JSON-aktig data att göra precis vad vi vill med!

Det svåraste med relationsdatabaser just nu - få ut exakta data från på hanterbart sätt!
Således, den största utmaningen jag har just nu med relationsdatabasen av typen MariaDB/SQL är att få de exakta data jag vill genom att kunna formulera "perfekta" SELECT queries med rätt JOINs, UNIONs eller vad nu som behövs. I EF Core är det verkligen mycket semantiskt trevligare.

Men jag undrar fortfarande om det sker mer efterbehandling på erhållna data i EF Core än vad man kan tro när man skriver sina LINQ-queries där. Hm... Den som nöter vidare får nog förr eller senare reda på saken!🕵️

På återseende!

Mvh,
WKL.
---------
✔️Kurs 1: HT2022 DT057G Datateknik GR (A), Webbutveckling I, 7,5 hp (distans)
✔️Kurs 2: HT2022 DT084G Datateknik GR (A), Introduktion till programmering i JavaScript, 7,5 hp (distans)
✔️Kurs 3: HT2022 DT068G Datateknik GR (B), Webbanvändbarhet, 7,5 hp (distans)
✔️Kurs 4: HT2022 DT200G Datateknik GR (A), Grafisk teknik för webb, 7,5 hp (distans)
✔️Kurs 5: VT2023 DT093G Datateknik GR (B), Webbutveckling II, 7,5 hp (distans)
✔️Kurs 6: VT2023 DT003G Datateknik GR (A), Databaser, 7,5 hp (distans)
✔️Kurs 7: VT2023 DT197G Datateknik GR (B), Webbdesign för CMS, 7,5 hp (distans)
✔️Kurs 8: VT2023 DT173G Datateknik GR (B), Webbutveckling III, 7,5 hp (distans)
✔️Kurs 9: HT2023 IK060G Informatik GR (A), Projektledning, 7,5 hp (distans)
✔️Kurs 10: HT2023 DT193G Datateknik GR (B), Fullstack-utveckling med ramverk, 7,5 hp (distans)
✔️Kurs 11: VT2023 DT162G Datateknik GR (B), Javascriptbaserad webbutveckling, 7,5 hp (distans)
✔️Kurs 12: VT2023 DT071G Datateknik GR (A), Programmering i C#.NET, 7,5 hp (distans)
✔️Kurs 13: VT2024 DT191G Datateknik GR (B), Webbutveckling med .NET, 7,5 hp (distans)
🚧Kurs 14: (Inväntar slutbetyg) VT2024 IG021G Industriell organisation och ekonomi GR (A), Affärsplaner och kommersialisering, 7,5 hp (distans)
🚧Kurs 15: (Exjobb pågår) VT2024 DT140G Datateknik GR (B), Självständigt arbete, 15 hp

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Är ditt problem alltså att du vill, för varje bil, även få ut dess egenskaper (alltså varje däck, categori m.m.)? För jag försöker förstå varför du blandar in JSON i din SQL-query

Du kan ju hämta ut alla bilar med dess tillhörande egenskaper genom LEFT JOIN

SELECT ... FROM Cars c LEFT JOIN Tires t ON c.id = t.car_id LEFT JOIN CategoriesCars ct ON c.id = ct.car_id LEFT JOIN Categories ca ON ct.category_id = ca.id ORDER BY c.id, t.id, ct.category_id

Du kommer få en "rad" tillbaka per egenskap såsom däck, kageori osv. Säg att du har 10 bilar, med 4 däck vardera (inga kategorier), det ger dig då 10 * 4 rader tillbaka.
Det är med denna data som du sedan bygger upp din datastruktur i PHP

Visa signatur

NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB

Permalänk
Skrivet av Pamudas:

Är ditt problem alltså att du vill, för varje bil, även få ut dess egenskaper (alltså varje däck, categori m.m.)? För jag försöker förstå varför du blandar in JSON i din SQL-query

Du kan ju hämta ut alla bilar med dess tillhörande egenskaper genom LEFT JOIN

SELECT ... FROM Cars c LEFT JOIN Tires t ON c.id = t.car_id LEFT JOIN CategoriesCars ct ON c.id = ct.car_id LEFT JOIN Categories ca ON ct.category_id = ca.id ORDER BY c.id, t.id, ct.category_id

Du kommer få en "rad" tillbaka per egenskap såsom däck, kageori osv. Säg att du har 10 bilar, med 4 däck vardera (inga kategorier), det ger dig då 10 * 4 rader tillbaka.
Det är med denna data som du sedan bygger upp din datastruktur i PHP

Tjo! Tack så mycket för svaret trots all min frustration!

Jag blandade in JSON_OBJECT för jag försökte hitta något annat sätt att göra det hela på. Så är på ett ungefär hur detta görs i relationsbaserade databasbranscherna: Du samlar på dig de data som du vill bygga en datastruktur av och så får du iterera igenom dubbletter och bygga upp en önskad datastruktur att sedan presentera (eller skicka vidare till) i frontend? Jag misstänker att även EF Core gör något liknande efter sin egen SQL-körning innan den skickar vidare de fantastiskt strukturerade data?

Jag ser oxå förresten att du kör tre sorteringar (ORDER BY) i slutet. Den första förstår jag, men hur kommer det sig att de två andra behövs? Bör inte de på något vis vara "bundna" till hur c.id sorteras som? Jag tänker när du sorterar utifrån en given kolumn inuti Excel. Eller blir det en specialare här för att de inte riktigt hör till samma tabeller?

Mvh,
WKL.

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Tjo! Tack så mycket för svaret trots all min frustration!

Jag blandade in JSON_OBJECT för jag försökte hitta något annat sätt att göra det hela på. Så är på ett ungefär hur detta görs i relationsbaserade databasbranscherna: Du samlar på dig de data som du vill bygga en datastruktur av och så får du iterera igenom dubbletter och bygga upp en önskad datastruktur att sedan presentera (eller skicka vidare till) i frontend? Jag misstänker att även EF Core gör något liknande efter sin egen SQL-körning innan den skickar vidare de fantastiskt strukturerade data?

Jag ser oxå förresten att du kör tre sorteringar (ORDER BY) i slutet. Den första förstår jag, men hur kommer det sig att de två andra behövs? Bör inte de på något vis vara "bundna" till hur c.id sorteras som? Jag tänker när du sorterar utifrån en given kolumn inuti Excel. Eller blir det en specialare här för att de inte riktigt hör till samma tabeller?

Mvh,
WKL.

Ja men typ.
I PHP är det hyffsat enkelt med arrayer, kolla om car.id existerar som nyckel - om inte, skapa objektet. Dvs: !isset(cars[car.id])

Om radens tire.id inte är null - lägg till i arrayen[car.id].tires
Gör samma med categories.

Ang. Order by så är det bara en preferens att sortera det så.
Fler kolumner i order by blir flera nivåer av sortering helt enkelt

Visa signatur

NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB

Permalänk
Medlem
Skrivet av medbor:

Blir på riktigt orolig, nej livrädd, att efter flera års utbildning inte ens skapa relativt säkra formulär

Man kan ibland undra vad ni lärt er

Det känns som att det är litet att kasta sten i glashus att dissa någon för ett sådant här fel när man själv inte klarar av att skriva två meningar på korrekt svenska...

Visa signatur

Bra, snabbt, billigt; välj två.

Ljud
PC → ODAC/O2 → Sennheiser HD650/Ultrasone PRO 900/...
PC → S.M.S.L SA300 → Bowers & Wilkins 607

Permalänk
Skrivet av Pamudas:

Ja men typ.
I PHP är det hyffsat enkelt med arrayer, kolla om car.id existerar som nyckel - om inte, skapa objektet. Dvs: !isset(cars[car.id])

Om radens tire.id inte är null - lägg till i arrayen[car.id].tires
Gör samma med categories.

Ang. Order by så är det bara en preferens att sortera det så.
Fler kolumner i order by blir flera nivåer av sortering helt enkelt

Jag ser nu att när jag tar ut "Bilar" med "Bildäck" och "Kategorier" så är det så att det finns exempelvis 12 olika "Kategorier" kopplade till en och samma "Bil" (Car_id) vilket då verkar göra så att för just en "Bil" då så får jag färst 12 rader med upprepade data från "Bil"-tabellen då övriga är LEFT JOIN. Det blir så för att just den Bil-Id har 12 referenser (Car_id) inuti Kategorier så det blir då 12 radmatchningar först? Här är också hela tiden "Tire_id = 1".

Sedan märker jag att efter 12 Kategorier så börjar den om igen för 12 Kategorier men nu på "Tire_id = 2" vilket nu blir det som upprepas 12 gånger såväl som alla övriga data från "Car"-tabellen. Jag har också fallet att en "Car" har 5st "Tire_id" kopplade till sig och en annan "Car" har 6st "Tire_id" kopplade till sig.

(5+6)(12) = 132 vilket stämmer överens med vad jag ser: 132 rader efter queryn. Det jag inte förstår är hur detta ens kan vara "hur man queryar SQL-databaser"-standard för nu har jag alltså bara 2 bilar, 11 däck och 13 kategorier inlagda och jag har redan 132 rader att plöja igenom. Hur blir det när det helt plötsligt är 1000 bilar med kanske 4000 däck?

Mvh,
WKL.

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Jag ser nu att när jag tar ut "Bilar" med "Bildäck" och "Kategorier" så är det så att det finns exempelvis 12 olika "Kategorier" kopplade till en och samma "Bil" (Car_id) vilket då verkar göra så att för just en "Bil" då så får jag färst 12 rader med upprepade data från "Bil"-tabellen då övriga är LEFT JOIN. Det blir så för att just den Bil-Id har 12 referenser (Car_id) inuti Kategorier så det blir då 12 radmatchningar först? Här är också hela tiden "Tire_id = 1".

Sedan märker jag att efter 12 Kategorier så börjar den om igen för 12 Kategorier men nu på "Tire_id = 2" vilket nu blir det som upprepas 12 gånger såväl som alla övriga data från "Car"-tabellen. Jag har också fallet att en "Car" har 5st "Tire_id" kopplade till sig och en annan "Car" har 6st "Tire_id" kopplade till sig.

(5+6)(12) = 132 vilket stämmer överens med vad jag ser: 132 rader efter queryn. Det jag inte förstår är hur detta ens kan vara "hur man queryar SQL-databaser"-standard för nu har jag alltså bara 2 bilar, 11 däck och 13 kategorier inlagda och jag har redan 132 rader att plöja igenom. Hur blir det när det helt plötsligt är 1000 bilar med kanske 4000 däck?

Mvh,
WKL.

Att "joina" tabeller är dyrt (väldigt dyrt) och ju fler tabeller du tar in, desto fler uppslag måste du därmed göra mot de andra tabellerna. Det är nackdelen med relationer helt enkelt. EF Core har precis samma begränsingar, bara att det inte sticker lika mycket i ögat Där har du även möjligheten att göra något som EF kallar Split Queries (Tänk dock på att SplitQueries inte alltid gör det snabbare)

Detta löses oftast med att begränsa den mängd data som faktiskt hämtas när du hämtar alla bilar. Ska du hämta all data så gör du det för en specifik bil, t.ex. en info-ruta som öppnas vid klick på bilen - eller använd någon form av paginering för att begränsa antalet rader som ska hämtas direkt.
Är tanken att du ska kunna hämta ut "antalet däck" som en bil har? Ja men kör något i stil med

SELECT COUNT(*) FROM Tires WHERE [CarId] = *bilens ID*

istället för att hämta ut all data. Att endast räkna antalet träffar är mycket snabbare.

Visa signatur

NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB

Permalänk
Skrivet av Pamudas:

Att "joina" tabeller är dyrt (väldigt dyrt) och ju fler tabeller du tar in, desto fler uppslag måste du därmed göra mot de andra tabellerna. Det är nackdelen med relationer helt enkelt. EF Core har precis samma begränsingar, bara att det inte sticker lika mycket i ögat Där har du även möjligheten att göra något som EF kallar Split Queries (Tänk dock på att SplitQueries inte alltid gör det snabbare)

Detta löses oftast med att begränsa den mängd data som faktiskt hämtas när du hämtar alla bilar. Ska du hämta all data så gör du det för en specifik bil, t.ex. en info-ruta som öppnas vid klick på bilen - eller använd någon form av paginering för att begränsa antalet rader som ska hämtas direkt.
Är tanken att du ska kunna hämta ut "antalet däck" som en bil har? Ja men kör något i stil med

SELECT COUNT(*) FROM Tires WHERE [CarId] = *bilens ID*

istället för att hämta ut all data. Att endast räkna antalet träffar är mycket snabbare.

Vore en lösning då med när man vill hämta "allt" om en given entitet att köra på LIMIT och köra med paginering? (EDIT: läste det nu det du skrev! ) Många webbplatser när du söker har ju paginering vilket jag antar delvis beror på detta faktum med databaser? Annars undrar jag hur företag har löst det hela med "Big Data" eller bara något där du kanske har kanske 100 kolumner i en Excel-fil med sedan tiotusentals rader? (eller vad som nu krävs för att det ska börja "kännas" lite) Då kanske inte ens relationsdatabaser används eller så kanske det implementeras med färre tabeller och därmed relationer?

När jag lärde mig grundläggande om databaser så var det fokus på att normalisera vilket i grunden verkar handla om att ha så många tabeller som möjligt för att förhindra motsägelser i databasen. Eller som en annan databaslärare har uttryckt som jag hört på YT: "Väx på längden!" (skapa ny tabell och/eller lägg till ny rad) istället för "växa på bredden" (lägga till ny kolumn i redan existerande tabell). Och detta verkar ju vara fint i teorin men i praktiken verkar det bli en "prestandakatastrof" när du ska sätta ihop flera tabeller för att du vill få en rejäl överblick men du har normaliserat bort nästan allt till enskilda tabeller?

Alternativ kanske är att hämta en tabell med alla Bilar och en tabell med alla Däck och så skickar man vidare all den data till någon annan processor som får sy ihop all data precis som jag kommer att behöva göra nu i PHP? Eller blir det egentligen samma sak i slutändan om man slår ihop den prestanda som då krävs från först databasen och sedan bearbetningen i PHP?

Jag antar att hur du än väljer att göra så kan du inte få allt!

Mvh,
WKL.

Visa signatur

<WKL:"En kodrad i taget!";/>

Permalänk
Medlem
Skrivet av WebbkodsLärlingen:

Alternativ kanske är att hämta en tabell med alla Bilar och en tabell med alla Däck och så skickar man vidare all den data till någon annan processor som får sy ihop all data precis som jag kommer att behöva göra nu i PHP? Eller blir det egentligen samma sak i slutändan om man slår ihop den prestanda som då krävs från först databasen och sedan bearbetningen i PHP?

Jag antar att hur du än väljer att göra så kan du inte få allt!

Mvh,
WKL.

Ja det är också ett alternativ. Men då sätter du högre krav på applikationen som bearbetar datan.
Tänk även på att din roundtrip till databasen kostar mycket i tid.

Stored procedures eller vyer i databasen är antagligen att rekommendera här.

Visa signatur

NZXT H510 Flow MSI B450 Tomahawk MAX
AMD Ryzen 5800X3D RX 7900XTX Kingston Fury 64GB

Permalänk
Medlem

Det jag som lekman förstått så ska databasen göra så mycket så möjligt. Det är det den är bäst på, att hantera data.

Sedan finns det ju index och saker man kan optimera i större databaser för att gynna vanligt förekommande frågor.