Adding to an existing datastore

I have an existing datastore of player data that I want to add new key/value pairs to.

The issue is that if I add a new key/value pair, then an existing player joins the game I get an error as I am trying to update a value that does not exist in their datastore.

I have attempted to create a new data table, with the new key/Value pair, then poplulate it with the old data, then save it without success.

Is it not possible to add new key/value pairs to an existing datastore? or (as I suspect) the problem is with how I have written my datastore module.

Any help or suggestions would be much appreciated.

1 Like

Using SetAsync with a key value pair should accomplish this.

Is that what you’re trying to do? I might have misunderstood your problem.

The player data is saved as a table. It has about 140 key/value pairs in it. Everything works fine except if I try to add a new key value pair like PlayerMoney = 0. Then when an exisiting player joins the game next time the code throws an error saying that the new key/value pair does not exist.

So is your error with trying to add the new key value pair, or is it with trying to index the value after adding it?

When I add the new key/value pair.

You should be able to do something like this:

exampleTable = ds:GetAsync(key)
exampleTable[newKey] = value
ds:SetAsync(key, exampleTable)

using updateasync is probably better but that’s the basic concept

Can I see the code you’re using to get and set the datastores and anything else relevant

I use a function like this in RbxWeb:

local function RecursivelyFix(DataChanged, DefaultData, PlayerData)
	for Index, Value in pairs(DefaultData) do
		if type(PlayerData[Index]) == "table" then
			RecursivelyFix(DataChanged, Value, PlayerData[Index])
		elseif PlayerData[Index] == nil then
			PlayerData[Index] = Value
			DataChanged = true
		end
	end
end

--[[**
	This is like DataStore2's ::GetTable() function, where it'll add missing data from the default data.
	@param [String] Key The key identifying the entry being retrieved from the DataStore.
	@param [NonNil] DefaultData The default data you are using as a base.
	@param [OptionalBoolean] OverwriteData Determines whether or not you overwrite the previous data. Defaults to true.
	@returns [Tuple<Boolean, Table>] Whether or not the attempt was successful and the updated value of the entry in the DataStore with the given key.
**--]]
function Generic:FixMissing(Key, DefaultData, OverwriteData)
	OverwriteData = OverwriteData or true
	assert(type(Key) == "string", string.format("bad argument #1 in Generic::FixMissing (string expected, got %s)", typeof(Key)))
	assert(DefaultData ~= nil, "bad argument #2 in Generic::FixMissing (non-nil expected, got nil)")
	assert(type(OverwriteData) == "boolean", string.format("bad arguments #3 in Generic::FixMissing (boolean expected, got %s)", typeof(OverwriteData)))

	local Success, PlayerData = self:GetAsync(Key)
	if Success and not PlayerData then
		warn("Warning! Player doesn't have any data!")
		if OverwriteData then
			PlayerData = DefaultData
			self:SetAsync(Key, PlayerData)
		end
	end

	if Success and PlayerData then
		assert(type(PlayerData) == "table", string.format("bad result type from Generic::GetAsync (expected table, got %s)", typeof(PlayerData)))
		local DataChanged = false
		RecursivelyFix(DataChanged, DefaultData, PlayerData)

		if OverwriteData and DataChanged then self:SetAsync(Key, PlayerData) end
		return Success, PlayerData
	else
		warn("Something went wrong while running Generic::FixMissing.", debug.traceback(2))
		return Success, PlayerData
	end
end

Should work for you, just use RecursivelyFix.

1 Like
local playerStatManager = {}
local dataStoreService = game:GetService("DataStoreService")
local playerData = dataStoreService:GetDataStore("PlayerData_v4_2.2.6")-- 1.8.6 used in playable version
local AUTOSAVE_INTERVAL = 120
local DATASTORE_RETRIES = 3
local sessionData = {}

-- Function to Update the Leaderboard
local function updateBoard(player)
	for i, e in pairs(sessionData[player])do
		player.leaderstats[i].Value = e
		
	end
end

function playerStatManager:ChangeStat(player,statName,changeValue)
	sessionData[player][statName] = sessionData[player][statName]+ changeValue
	updateBoard(player)
end

local function dataStoreRetry(dataStoreFunction)
	local tries = 0
	local success = true
	local data = nil
	
	repeat
		tries = tries + 1
		success = pcall(function() 
			data = dataStoreFunction()
		end)
		if not success then
			wait(1)
		end
	until
	tries == DATASTORE_RETRIES or success
	
	if not success then
		error("Could not access DataStore! Data might not save!")
	end
	return success,data
end

-- Function to retrieve players data from the DataStore
local function getPlayerData(player)
	return dataStoreRetry(function()
		return playerData:GetAsync(player.UserId)
	end)
end

-- Function to save players data to DataStore
local function savePlayerData(player)
	if sessionData[player] then
		return dataStoreRetry(function()
			return playerData:SetAsync(player.UserId,sessionData[player])
		end)
	end
end

local function setupPlayerData(player)
	local success,data = getPlayerData(player)
	if not success then
		-- Could not access DataStore, set session data for player to false
		sessionData[player] = false
	else
		if not data then
			-- DataStores are working, but no data for this player
			sessionData[player] = {MummyCount = 0, TotalEggs = 0, TotalTiles = 0, TotalPurple = 0, TotalOrange = 0, TotalGreen = 0, TotalYellow = 0, TotalBlue = 0, TotalRed = 0, BuyClothes = 0, TotalGems = 0, ChainDomeName = 0, TesterPass = 0, VipGamePass = 0, TotalGold = 0, RedBonus = 0, BlueBonus = 0, YellowBonus = 0, GreenBonus = 0, OrangeBonus = 0, PurpleBonus = 0, GoldBonus = 0, RainbowCatPants = 0, RainbowCatShirt = 0, RainbowButterflyPants = 0, RainbowButterflyShirt = 0, RobotPants = 0, RobotShirt = 0, RainbowBirdPants = 0, RainbowBirdShirt = 0, RainbowUnicornPants = 0, RainbowUnicornShirt = 0, BlueSpacePants = 0, BlueSpaceShirt = 0, JunglePants = 0, JungleShirt = 0, SandPants = 0, SandShirt = 0, PurpleHeartPants = 0, PurpleHeartShirt = 0, RainbowBeePants = 0, RainbowBeeShirt = 0, RedLavaPants = 0, RedLavaShirt = 0, PinkLeopardPants = 0, PinkLeopardShirt = 0, GlitterPants = 0, GlitterShirt = 0, BlackWhiteCirclesPants = 0, BlackWhiteCirclesShirt = 0, RainbowHeartsPants = 0, RainbowHeartsShirt = 0, MultiColoursPants = 0, MultiColoursShirt = 0, PurpleBlueTrianglePants = 0, PurpleBlueTriangleShirt = 0, BlueShardPants = 0, BlueShardShirt = 0, GreyCubesPants = 0, GreyCubesShirt = 0, MettalicGreyStripesPants = 0, MettalicGreyStripesShirt = 0, PinkSpacePants = 0, PinkSpaceShirt = 0, RedSpacePants = 0, RedSpaceShirt = 0, GreenTrianglesPants = 0, GreenTrianglesShirt = 0, ColourBubblesPants = 0, ColourBubblesShirt = 0, WaterPants = 0, WaterShirt = 0, YellowButterflyPants = 0, YellowButterflyShirt = 0, PenguinSpacePants = 0, PenguinSpaceShirt = 0, RainbowStripesPants = 0, RainbowStripesShirt = 0, ColorSpotsPants = 0, ColorSpotsShirt = 0, BlueZigsPants = 0, BlueZigsShirt = 0, BlueAndGreenCheckPants = 0, BlueAndGreenCheckShirt = 0, BlackAndWhitePants = 0, BlackAndWhiteShirt = 0, SpaceStrawberryPants = 0, SpaceStrawberryShirt = 0, GoldLovePants = 0, GoldLoveShirt = 0, MettalicGreyPants = 0, MettalicGreyShirt = 0, PurpleButterflyPants = 0, PurpleButterflyShirt = 0, SpaceLovePants = 0, SpaceLoveShirt = 0, SpaceBatPants = 0, SpaceBatShirt = 0, ColorCubesPants = 0, ColorCubesShirt = 0, MultiSpacePants = 0, MultiSpaceShirt = 0, BlueStarsPants = 0, BlueStarsShirt = 0,PinkSpaceSwirlPants = 0, PinkSpaceSwirlShirt = 0,PinkCubePants = 0, PinkCubeShirt = 0, PurpleGalaxyPants = 0, PurpleGalaxyShirt = 0, BestChain = 0, BirdPet = 0, BunnyPet = 0, ButterflyPet = 0, DragonPet = 0, FishPet = 0, MousePet = 0,  SkunkPet = 0, UnicornPet = 0, DoggerPet = 0, CatPet = 0, SpawnedPets = 0, GoldSpent = 0, TotalGoldStat = 0, GoldFromRewards = 0,GoldFromWorking = 0, GoldFromBoard = 0, PlayerDistance = 0, GreenComplete = 0, GreenGoal = 5, Green = 0, RedComplete = 0, BlueComplete = 0, YellowComplete = 0, BoardPart = 0, Boards = 0, Yellow = 0, YelGoal = 5, Red = 0, RedGoal = 5, Blue = 0, BlueGoal = 5, Orange = 0, OrangeGoal = 5, OrangeComplete = 0, Purple = 0, PurpleGoal = 5, PurpleComplete = 0,  Gold = 0, Claimed = 0}
			savePlayerData(player)
		else
			-- DataStores are working and we got data for this player
			sessionData[player] = data
		end
	end
	updateBoard(player)
end

-- Function to run in the background to periodically save players data.
local function autoSave()
	print("Autosaveed!")
	while wait(AUTOSAVE_INTERVAL) do
		for player, data in pairs(sessionData) do
			savePlayerData(player)
		end
	end
end

-- Bind setupPlayerData to PlayerAdded to call it when player joins.
game.Players.PlayerAdded:Connect(setupPlayerData)

-- Call savePlayerData on PlayerRemoving to save player data when they leave.
-- Also delete the player from the sessionData, as the player isnt in-game anymore.
game.Players.PlayerRemoving:Connect(function(player)
	
	savePlayerData(player)
	sessionData[player] = nil
	
	print("Data Saved!")
	
	
end)

-- Start running autosave function in the background
spawn(autoSave)

-- Return the PlayerStatManager table so external scripts can access it.
return playerStatManager

I have a feeling it is the updateBoard function and when it is called.

Here is the errors, if I add a new value

10:40:31.483 - ServerScriptService.playerStatManager:11: bad argument #1 to ‘pairs’ (table expected, got number)

10:40:31.483 - Stack Begin

10:40:31.484 - Script ‘ServerScriptService.playerStatManager’, Line 11 - upvalue updateBoard

10:40:31.484 - Script ‘ServerScriptService.playerStatManager’, Line 75

10:40:31.484 - Stack End

I see the problem, you are calling updateBoard regardless of the value of sessionData[player]

in that setupdata function you have, if getting the data fails it sets sessionData[player] to false, a bool value, not a table.

Try checking that sessionData doesn’t = false or nil before iterating through it in your updateBoard function

ok, cool, i’ll have a play with it for awhile. Thanks for your help. Will post back when/if I get it sorted. Cheers

1 Like

You sir are a legend. Just added that 1 conditional statement, and it seems to have done the trick. Just could not find it myself. Deeply appreciated. :+1:

local function updateBoard(player)
if sessionData[player] then
		for i, e in pairs(sessionData[player])do
			player.leaderstats[i].Value = e
			
		end
	end
end
8 Likes