DataStore Assistance

Hello!
I have some questions reguarding the usage of a Datastore when ever it comes to scalibility. I used to store all of the Data in a nosql database off site (RethinkDB) but, I wanted to expand my knowledge by attempting to use Roblox’s DataStores. The project is a Hospital where you will enter with symptoms (Runny Nose, Loss Of Feeling, ETC…); and then the Dr/Patient care team will diagnose you and then log findings so you’re hospital record can be pulled with information reguarding you’re hospital vist. I have reached out to @SteadyOn for some help and he has helped a little bit with this but, if you have time to either continue his help or know where I could look for documenation/people with similar questions you can either DM me on Roblox, DevForum or on Discord - Austin#6574 . I have attached a format that I had in mind and an example along with SteadyOn’s attempt to help.

Format: (Scalable DataStore)
CaseID - (AI Value in MYSQL (Counts up)
Patient Name -
Reported Symptoms - (Symptom, Location, Pain-Scale)
Diagnosed For -
Supervisors
Lead Dr -
Head Nurse
Dr’s -
Nurses -
Medications Given - (Type, Dosages & Time Medications Adminstred)
Operations Preformed - (Type, Kind & Time)
TOD - (OS Time, N/A If Not Needed)
Other Information - (Able to Input Text Response)
Visit Start Time - (OS Time)
Visit End Time - (OS Time)

Example:
CaseID - 1
TOD - N/A
Patient Name - EvolutionaryCode
Reported Symptoms - {{Runny Nose, Nose, 1}, {Swollen Head, Head, 9}, {Fatigue, Genneral, 4}}
Diagnosed For - Swollen Foot Snydrom
Supervisors - SteadyOn
Lead Dr - Spooks
Head Nurse - Tiger
Dr’s - Sans, Bob, Joe, Billy
Nurses - Ted, William, YOLO
Medications Given - {Morphine, 2mg , 2am}
Operations Preformed - {{Breathing Tube, Life Saving, 1am},{Appendectomy, Surgical, 4am}}
Other Information - Billy Was All Swollen in the foot area removed nail
Visit Start Time - 1pm
Visit End Time - 1am

@SteadyOn 's Attempt

local existingData = ds:GetDataStore("StoredPatientData"):GetAsync(plr.UserId)
if existingData == nil then
    existingData = {}
end
-- to add a case
local caseNumber = #existingData + 1
existingData[tostring(caseNumber)] = {
    ["Name"] = "Patient'sName",
    ["Symptoms"] = "Their symptoms",
    ["Diagnosed"] = "Diagnosed with",
    ["Supervisors"] = {player1, player2, player3}
    ["Lead Doctor"] = "whoever is the lead doctor",
    ["Head Nurse"] = "whoever is the head nurse",
    ..etc...
}
-- to save it again
ds:GetDataStore("StoredPatientData"):GetAsync(plr.UserId, existingData)

Thank You!

Datastores are quite easy to use once you get the hang of them. There’s a ton of tutorials on the DevForum about how to use datastores and they are all typically quite easy to follow. I highly suggest reading those tutorials and the tutorial on the wiki about how to save player’s data as they will be a godsend for you.

Thank You!

I just looked over this, and I noticed it really doesn’t provide an insight to setting up a multilayered table. I was wondering do you have a Discord so that way I could message you, of course if you’d be willing to help me further.

I don’t really understand what you’re asking? The code example you gave should work, given you replace the last line of code where it says “GetAsync” with “SetAsync”. With DataStore, you can store a table just like you store it in Lua memory, except for Roblox types like Instances, Color3, Vector3, etc…

I have made a diagram as it seems that people don’t seem to understand my problem or my end goal. This should make more sense and for those still wondering it’s more of how to do I create a DataStore table to complete this action.

Are you asking for (us) to attempt to create a datastore structure for your example? I read your post 5 times and I don’t understand what you need help with.

Yes, If you wouldn’t mind I’m a very visual learner so me just reading a wiki I struggle with. I really struggle with tables within tables as Roblox has nothing further on tables then the basics.

Here’s something I cooked up - mind you, I might have missed a few things.

Visual chart


I made this with XMind 7, it’s free.

Raw data:
> EX :: cases:GetAsync("CaseId61")

{
	Symptoms = {"Broken"},
	Location = {"Hand"},
	PainLevel = 5.7,
	OtherInfo = "",
	Username = "Bob1",
	UserId = 23525132,
	Solved = false
}

> EX :: players:GetAsync("23525132")

{
	Cases = {
		"CaseId62",
		"CaseId61",
		"CaseId60"
	}
}

> EX :: get ordered datastore blah blah

for pos, x in ipairs(recentCasesOrderedDatastorePage) do
	local tick = tonumber(x.key)
	local caseId = "CaseId" .. x.value
end
1 Like

The only issue is this section, does it have to be this many lines like this or can I have A “Table in a table in a table?”

Symptoms = {“Broken”},
Location = {“Hand”},
PainLevel = 5.7,
OtherInfo = “”,

That should be fine. I often go to 3 layers of tables in my datastores too as I have player data split into currency and preferences, and then preferences split into categories. Below is an example of how I set out my table. data is then saved using a single call to SetAsync and retrieved using a single call to GetAsync.

-- A single user's player data table
data = {
    currency = {
        points = 1000,
        experience = 40,
    },
    preferences = {
        audio = {
            menu_music = 0.5,
            game_music = 1,
        },
        display = {
            draw_distance = 2048,
        },
    },
}

Although mine is a static layout, the same nesting ability applies to your situation too. I do a similar thing with saving purchases and transactions using nested tables that I simply add the latest purchase to for that player.

So assuming you’re trying to load a users data and put it in to a screen gui?

Does the user have a datastore already made? If not have you created a default datastore for them?
You could do this multiple ways, such as creating one from scratch or replicating one. Very similar to how ScriptOn showed.

DefaultData = { -- Default data, only used if the user has never played before
	['Name'] = nil,
	["Symptoms"] = nil,
    ["Diagnosed"] = nil, -- Notice how this is NIL, you don't need to have this entire line due to it being nil. But it helps you read and understand where your datastore is.
    ["Supervisors"] = nil,
    ["Lead Doctor"] = nil, -- If you wanted the Lead Doctor to always be John Doe then you set it here, etc..
    ["Head Nurse"] = nil,
};

game:GetService("Players").PlayerAdded:connect(function(plr) 
	pcall(function() -- This will prevent any errors given (Like on the rare case that you are sending and recieving to much data)
		local Data = DATASTORE:GetAsync(plr.UserId)
		if Data == nil then
			Data = DefaultData
		end
		DATASTORE:SetAsync(plr.UserId..DATAKEY,Data) -- We save the users data here (just incase it was the users first join)
	end)
end)

As I said in the comment above… you don’t need to set the values to Nil (you could just remove them all together) but it helps organize and lets you read what your data is named, etc…

You can then assign a user data by simply changing the value’s we set to nil. So if you wanted a user to need to check in at the desk you would make a script to change the users table.

local Data = DATASTORE:GetAsync(plr.UserId) -- If you don't want to always load the users data in then you can make globals.
Data.Name = tostring(plr.Name)
Data.Symptons = sympton1,sympton2 -- Sympton1 would be whatever the user said or whatever your randomizer etc.. is

Although if you are using multiple scripts then you may want to use a global to add data from one script to another. I would recommend sending the data with it (that way you don’t need to load in the data over and over! this will also prevent users from spamming it.

_G.updateValue = function(plr,updating,newvalue)
	pcall(function()
		local Data = DATASTORE:GetAsync(plr.UserId) 
		Data[updating] = newvalue
	end)
end

Earlier you were asking about if you can have a table inside of a table! This is completely fine. You can set your defaultdata or the users data to whatever you want.

DefaultData = { -- Default data, only used if the user has never played before
	['Name'] = nil,
	["Symptoms"] = {
		Symptom1 = "Bleeding in the far left toe";
		Symptom2 = "Massive ear ache";
	};
    ["Diagnosed"] = {
		BrokenLeftArm = false;
		BrokenRightArm = false;
		NailInToe = false;
	};
    ["Supervisors"] = {
		Supervisor1 = nil;
		Supervisor2 = nil;
	};
    ["Lead Doctor"] = nil, -- If you wanted the Lead Doctor to always be John Doe then you set it here, etc..
    ["Head Nurse"] = nil,
};

Becuase of the bleeding toe, you know that its most likely a nail in the toe! You can set this to true very simply.

Data.Diagnosed.NailInToe = true

Now if you wanted to make Supervisor1 John Doe then you can simply change that value like we did before!

Data.Supervisors.Supervisor1 = "John Doe"

But lets say you wanted to add Supervisors and didn’t just want two! Then you can simply make a new value and set it to nil via scripts.

if Data.Supervisors["Supervisor3"] = nil then
	Data.Supervisors["Supervisor3"] = Jane Doe
end

But what if we wanted to make this more advanced? What if we wanted to check the Diagnostics and then give the user a sympton based on it? That is also very plausible.

if Data.Symptoms.Symptom1 == "Bleeding in Toe" then
	if Data.Symptoms.Symptom2 == "Headache" then
		Data.Diagnosed = "Nail in Toe" -- Or Data.Diagnosed.NailInToe = tru3
	end
	if Data.Symptons.Sympton2 == "Pain in Toe" then
		Data.Diagnosed = "Cut on Toe" -- Or Data.Diagnosed.CutOnToe = tru3
	end
end

Now lets put everything together to make a working script!
(I have not testing this script, if it doesn’t work then I apologize ahead of time)

DefaultData = { -- Default data, only used if the user has never played before
	['Name'] = nil,
	["Symptoms"] = {
		Symptom1 = nil;
		Symptom2 = nil;
	};
    ["Diagnosed"] = {
		BrokenLeftArm = false;
		BrokenRightArm = false;
		NailInToe = false;
		CutInToe = false;
	};
    ["Supervisors"] = {
		Supervisor1 = nil;
		Supervisor2 = nil;
	};
    ["Lead Doctor"] = nil, -- If you wanted the Lead Doctor to always be John Doe then you set it here, etc..
    ["Head Nurse"] = nil,
};

local DataStoreService = game:GetService("DataStoreService")
local DATASTORE = DataStoreService:GetDataStore("UserData")

function checkSymptoms(plr)
	local Data = DATASTORE:GetAsync(plr.UserId)
	if Data.Symptoms.Symptom1 == "Bleeding in Toe" then
    	if Data.Symptoms.Symptom2 == "Headache" then
    		Data.Diagnosed.NailInToe = true;
    	end
    	if Data.Symptons.Sympton2 == "Pain in Toe" then
    		Data.Diagnosed.CutInToe = true;
    	end
    end
end

_G.updateValue = function(plr,updating,newvalue) -- The player, the value to change, the new value
	pcall(function()
		local Data = DATASTORE:GetAsync(plr.UserId)
		updating = newvalue
	end)
end

game:GetService("Players").PlayerAdded:connect(function(plr) 
	local Data = DATASTORE:GetAsync(plr.UserId)
	pcall(function() -- This will prevent any errors given (Like on the rare case that you are sending and recieving to much data)
		if Data == nil then
			Data = DefaultData
		end
		DATASTORE:SetAsync(plr.UserId,Data) -- We save the users data here (just incase it was the users first join)
	end)
	wait(6) -- Wait 10 seconds then update the symptoms
	_G.updateValue(plr,Data["Symptoms"]["Symptom1"],"Headache")
	checkSymptoms(plr)
end)

I hope this helped! If you need more help feel free to respond or send me a direct message!

2 Likes