M: I'm using the mobile database program Ninox. I think that's how it's pronounced. N-I-N-O-X. Can you tell me if there's an easy way when inputting to make sure that whatever is put in is not a duplicate entry when it's the, well I don't know if it's a the question, but when it is containing duplicate information. Like on a karaoke database, I don't want to end up putting in a song twice if it already exists.
G: Yep, you’ve got the name right—it’s Ninox 👍. And yes, you can absolutely set it up so you don’t accidentally add duplicates, though how you do it depends a little on whether you want Ninox to block the duplicate or just warn you.
SongTitle + " - " + Artist), and mark it
as “unique” in the field properties.let dupe := (select Songs where SongTitle = this.SongTitle and Artist = this.Artist);
if count(dupe) > 1 then
alert("This song already exists in the database!");
delete this
end
That way, if you add the same song twice, Ninox pops up a message and removes the duplicate.
✨ For a karaoke list, the combined field marked unique approach (Option 1) is usually the easiest—it’s quick, clean, and doesn’t require scripting unless you want more control.
Do you want me to sketch out how to set up that combined Song-Artist unique field step by step, so you don’t even have to touch scripting?
M: What if I'm using a CSV import? Would I be able to do anything then that would prevent duplicates in case there already existed a record with the same information or if somehow I ended up with two copies of the same entry in the CSV itself?
G: Ah, good catch — imports are trickier because they can flood your table in one go 🚰. Ninox won’t automatically de-dupe during CSV import, but you can set things up so duplicates either get blocked or are easy to purge afterward.
let dups := select Songs group by SongTitle, Artist having count(*) > 1;
alert("Found " + text(count(dups)) + " duplicates.");
From there, you could expand it to list or delete the extras.
💡 Practical tip:
If you expect to import CSVs regularly, it’s usually safest to:
SongTitle + " - " + Artist).That way, you can confidently load huge CSVs without manually scanning for doubles.
Would you like me to show you exactly how to set up that combined unique field so CSV imports automatically respect it?
M: I'm not seeing any option to choose a unique field. Do you have any idea where that would be? I'm not using the paid version. Is it something to do with that?
G: You're absolutely right to notice that there’s no setting labeled “Unique Field” in Ninox—even in the paid version. That kind of built-in unique constraint simply doesn’t exist yet. It’s not a permissions issue or tied to your version. It’s a limitation of the platform itself. citeturn0search1
From the Ninox community forum:
“There is no ‘easy’ way to mark a field as ‘unique’ … The only way is to create a script that ensures there is no duplicate.” citeturn0search1
So, sadly, even though SQLite (which Ninox uses under the hood) supports unique constraints, Ninox has not exposed that functionality in its UI.
Since there's no native unique field, you'll want to build a workaround using scripting. Here are the most common approaches:
Use a script on the field or record that checks for duplicates and alerts the user.
For example, if your fields are Song Title and Artist:
let title := this.SongTitle;
let artist := this.Artist;
if count(select Songs where SongTitle = title and Artist = artist) > 1 then
alert("This song (and artist) already exists!");
// Optionally clear a field or delete this record:
this.SongTitle := null;
end
This can run after the record is created or after a specific field is updated to immediately catch duplicates. citeturn0search4turn0search15
You can add a Read-Only Formula field that marks records as “Duplicate” or “Unique”:
let t := this;
if count(select Songs where SongTitle = t.SongTitle and Artist = t.Artist) > 1 then
"Duplicate"
else
"Unique"
end
This way, duplicates are flagged visually and can be filtered or sorted out. citeturn0search5
If you're using CSV imports, you can write a custom import routine (via script) that:
Here's an example pattern (adapted for your karaoke songs):
for row in csvRows do
let existing := first(select Songs where SongTitle = row.Title and Artist = row.Artist);
if existing then
// Optionally update fields
existing.Album := row.Album;
// ... other updates
else
let newRec := create Songs;
newRec.SongTitle := row.Title;
newRec.Artist := row.Artist;
newRec.Album := row.Album;
end
end
This is similar to a real-world example shared for preventing duplicate products during import. citeturn0search3
If you're on the web version, there's a sample CSV import tool that uses a dashboard and server-side scripting to check duplicates across tables while importing. It’s more elaborate, but powerful if you want to streamline regular imports. citeturn0search10
| Problem / Goal | Solution Approach |
|---|---|
| Prevent duplicates on manual entry | Use Trigger scripts (After Create /
After Update) |
| Flag duplicates for review | Use a Formula field to label “Duplicate” or “Unique” |
| Avoid duplicate records from imports | Scripted import: select → update or create logic |
| Automate import with dedupe logic | Use dashboard + server-side script (web-only) |
Would you like a ready-made Ninox script example tailored exactly to your Song-Artist karaoke setup?
M: Here's something more advanced. What if I'm trying to create something a little bit more complex? I want a field... No, scratch that. I think I was using the wrong words. I want to create a record which can contain a variable number of predefined combinations of various qualities. Okay, let's just stop being vague. I'm trying to create a database based on the Marvel Puzzle Quest phone game, just to see if I can do it. And with a little bit of utility, because in some of the actual puzzle stages, you have to perform actions like, I don't know, create blue tiles. So, what I'm trying to do is create a record that contains a variable list of effects that each power can have. So, some powers can transform any tile into a blue one, while others will randomly transform only basic tiles into blue tiles. And a third one might transform any attack tiles that are selected into a blue tile. And so, I'd like to be able to include a variable number of those kinds of descriptors into the definition record. Is that even possible?
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: TL;DR: Yes. Model each Power as a record with a child table of Effects. Each Effect is one “descriptor tuple” (e.g., Transform → Blue → Random → Only Basic Tiles). Because Effects are separate records linked to the Power, you can attach any number of them. Use small lookup tables (or Choice fields) for the parts of the tuple, and add a tiny script to prevent duplicate tuples on the same Power.
Name (text)Character (reference to Characters, optional)Action (Choice or reference) — e.g., Create / Transform
/ Destroy / ConvertColor (Choice) — Red / Blue / Yellow / etc.Selection (Choice) — Any / Random / Selected /
AreaTile Class (Choice or reference) — Any / Basic / Attack
/ Strike / Protect / Countdown / Charged…Qty (number, optional) — e.g., “up to 3 tiles”Notes (text, optional)Power (reference back to Powers; this is the parent
link)EffectKey (Formula; see below) — used to block
duplicatesTip: If you want a very stable vocabulary, make lookup tables (e.g.,
EffectAction,TileClass) and reference them. If you prefer simple and quick, use Choice fields.
In the Powers form, add a subtable
view of Effects. That lets you add as many Effect
rows as you need for that Power.
Add a Formula field on Effects named
EffectKey:
// Effects.EffectKey (Formula)
upper(text(Action))
+ "|" + upper(text(Color))
+ "|" + upper(text(Selection))
+ "|" + upper(text('Tile Class'))
+ "|" + if Qty then text(Qty) else "" end
Then in Effects → Trigger after create (or after update if you want to catch edits, too):
let k := this.EffectKey;
let p := this.Power;
if count(select Effects where Power = p and EffectKey = k) > 1 then
alert("Duplicate effect for this power.");
delete this
end
Result: you can add as many Effects as you want, but not the same tuple twice for the same Power.
Add a Formula field on Effects:
// PrettySummary (Formula)
switch Action do
case "Create" then "Create "
case "Transform" then "Transform "
case "Destroy" then "Destroy "
else text(Action) + " "
end
+ if Qty then text(Qty) + " " else "" end
+ lower(text('Tile Class')) + " "
+ if Color then "tiles to " + lower(text(Color)) + " " else "" end
+ "(" + lower(text(Selection)) + ")"
And on Powers, add a Formula to concatenate all child summaries:
// EffectsSummary on Powers (Formula)
concat(
(select Effects where Power = this),
"\n",
PrettySummary
)
That gives each Power a compact, readable list of its variable effects.
PowerCode).Effects
fields and link each row to a Power (e.g., via
Power := first(select Powers where Name = row.PowerName) if
you do a scripted import).Effects or factor them into additional tiny lookup
tables.PatchVersion (text/choice) to both Powers
and Effects so you can snapshot changes over time
without overwriting history.delete this with
this.Notes := "DUPLICATE"; and prevent saving by using an
After update script that resets fields and shows an
alert.This structure gives you exactly what you described: each Power holds a variable-length list of standardized effect descriptors, with clean de-duplication and easy display.