অ্যাডমিন ফিল্টারের জন্য PostgreSQL generated columns দিয়ে দ্রুততা
PostgreSQL generated columns কীভাবে অ্যাডমিন স্ক্রিনের ফিল্টার ও সোর্টিং দ্রুত করতে পারে, উদাহরণ ও দ্রুত চেকিং সহ পড়ুন।

কেন অ্যাডমিন স্ক্রিন দ্রুত ধীর এবং জটিল হয়ে ওঠে\n\nঅ্যাডমিন স্ক্রিনগুলো সাধারণত সরলভাবে শুরু হয়: একটি টেবিল, কয়েকটা ফিল্টার, হয়তো “নতুনগুলো প্রথম” নামে একটি সোর্ট। তারপর কাজ বাড়তে শুরু করে। সাপোর্ট চাইবে নাম, ইমেইল, ফোন দিয়ে খোঁজ করতে। সেলস চাইবে “শেষ কার্যকলাপ” অনুযায়ী সাজাতে। ফাইন্যান্স চাইবে “বকেয়া ব্যালান্স” অনুযায়ী ফিল্টার। প্রতি অনুরোধে শর্ত, জয়েন, এবং অতিরিক্ত গণনা বাড়ে।\n\nঅধিকাংশ অ্যাডমিন তালিকা একই কারণে ধীর হয়: প্রতিটি ক্লিক কুয়েরি বদলে দেয়। ফিল্টার ও সোর্ট করার সময় ডাটাবেস অনেক রো স্ক্যান করতে পারে, বিশেষত যখন কুয়েরি প্রতিটি রোতে একটি মান হিসাব করে তারপর সিদ্ধান্ত নেয় কোনটি ম্যাচ করে।\n\nএকটি সাধারণ মোড় আসে যখন WHERE এবং ORDER BY এক্সপ্রেশন দিয়ে ভরে ওঠে। সাধারণ কলামের বদলে আপনি lower(email), date_trunc('day', last_seen_at), বা একাধিক স্ট্যাটাসকে এক “বাকেট” এ মানচিত্র করতে CASE ব্যবহার করে ফিল্টার করেন। এই এক্সপ্রেশনগুলো শুধুই ধীর নয় — এগুলো SQL পড়তে কঠিন করে, ইনডেক্স করা কঠিন করে, এবং সহজে ভুল হওয়ার সুযোগ তৈরি করে।\n\nঅগোছালো অ্যাডমিন SQL সাধারণত কয়েকটি পুনরাবৃত্ত প্যাটার্ন থেকেই জন্মায়:\n\n- একটি “সার্চ” ইনপুট যা বিভিন্ন নিয়মে একাধিক ফিল্ড চেক করে\n- উৎপন্ন মান দিয়ে সোর্ট করা (ফুল নেম, প্রাধান্য স্কোর, “শেষ অর্থপূর্ন ইভেন্ট”)\n- ব্যবসায়িক নিয়মগুলো স্ক্রিন জুড়ে কপি করা (active vs inactive, paid vs overdue)\n- ছোট “হেল্পার” টুইক (trim, lower, coalesce) ছড়িয়ে থাকা\n- একই গণিত করা মান তালিকা, ফিল্টার, এবং সোর্টিং এ বারবার ব্যবহার হয়\n\nটিমগুলো প্রায়ই এটি অ্যাপ লেয়ারে লুকাতে চায়: ডায়নামিক কুয়েরি বিল্ডার, শর্তভিত্তিক জয়েন, বা কোডে প্রিকম্পিউটেড মান। তা কাজ করলেও, এটি UI এবং ডাটাবেসের মধ্যে লজিক ভাগ করে দেয়, ফলে ধীর কুয়েরি ডিবাগ করা কষ্টসাধ্য হয়ে পড়ে।\n\nলক্ষ্য সোজা: দ্রুত কুয়েরি যেগুলো পড়তে সহজ থাকে। যখন কোনো হিসাব করা মান বারবার অ্যাডমিন স্ক্রিনে দেখা যায়, PostgreSQL generated columns সেই নিয়মকে এক জায়গায় রেখে ডাটাবেসকে অপ্টিমাইজ করার সুযোগ দেয়।\n\n## জেনারেটেড কলাম সরল ভাষায়\n\nএকটি generated column হচ্ছে টেবিলের এক সাধারণ কলাম যার মান অন্য কলামগুলো থেকে হিসাব করা হয়। আপনি মানটি নিজে লিখেন না। PostgreSQL আপনার দেওয়া এক্সপ্রেশন ব্যবহার করে এটি পূরণ করে।\n\nPostgreSQL-এ generated columns স্টোর হয়। PostgreSQL একটি রো ইনসার্ট অথবা আপডেট হলে মানটি গণনা করে, তারপর ডিস্কে অন্য কলামের মতোই সংরক্ষণ করে। অ্যাডমিন স্ক্রিনের জন্য এটিই সাধারণত চান: দ্রুত রিড এবং গণিত করা মান ইনডেক্স করার ক্ষমতা।\n\nএটি প্রতিটি কুয়েরির ভেতরে একই গণনা বার বার করার থেকে আলাদা। যদি আপনি বারবার লিখেন WHERE lower(email) = lower($1) বা ORDER BY last_name || ', ' || first_name — আপনি বারবার খরচ দেবেন এবং SQL গোলমেলে হবে। একটি generated column সেই পুনরাবৃত্ত গণনাকে টেবিল সংজ্ঞায় সরিয়ে দেয়। আপনার কুয়েরি সরল হয়, এবং ফলাফল সব জায়গায় সঙ্গতিপূর্ণ থাকে।\n\nযখন সোর্স ডেটা বদলে যায়, PostgreSQL স্বয়ংক্রিয়ভাবে ঐ রো-এর জন্য generated মান আপডেট করে। আপনার অ্যাপকে তা স্মরণ রাখার দরকার নেই।\n\nএকটি সহজ মেন্টাল মডেল:\n\n- সূত্র একবার সংজ্ঞায়িত করুন।\n- PostgreSQL লিখার সময় এটি গণনা করে।\n- কুয়েরি এটি সাধারণ কলামের মতো পড়ে।\n- যেহেতু এটি স্টোর করা হয়, আপনি এটি ইনডেক্সও করতে পারেন।\n\nপরবর্তী সময়ে সূত্র বদলালে, আপনার একটি স্কিমা পরিবর্তন লাগবে। এটি যেকোন মাইগ্রেশনের মতো পরিকল্পনা করুন, কারণ বিদ্যমান রো গুলোও নতুন এক্সপ্রেশনের সঙ্গে আপডেট হবে।\n\n## ফিল্টার ও সোর্টিংয়ে হিসাব করা ফিল্ডের ভালো ব্যবহার\n\nGenerated columns সেই সব ক্ষেত্রে উজ্জ্বল যখন মানটি সবসময় অন্য কলাম থেকে নির্ভর করে এবং আপনি সেটিতে প্রায়ই ফিল্টার বা সোর্ট করেন। এক-বারের রিপোর্টের জন্য এগুলো ততটা উপকারী নয়।\n\n### ব্যবহারকারীরা বাস্তবে যে সার্চ ফিল্ড চান\n\nঅ্যাডমিন সার্চ খুব কমই “শুদ্ধ” সার্চ হয়। মানুষ আশা করে সার্চ বক্স ঝামেলায়, অপ্রতিষ্ঠিত কেসিং এবং অতিরিক্ত স্পেস সামলাবে। যদি আপনি একটি generated “search key” সংরক্ষণ করেন যা আগে থেকেই নরমালাইজ করা, আপনার WHERE ক্লজ পড়তে সহজ থাকবে এবং স্ক্রিন জুড়ে আচরণ একই থাকবে।\n\nভালো প্রার্থী হতে পারে: একত্রিত ফুল নাম, ছোট হাতের বানানে ট্রিম করা টেক্সট, অতিরিক্ত স্পেস কেটে দেয়া, অথবা একাধিক ফিল্ড থেকে পাওয়া স্ট্যাটাস লেবেল।\n\nউদাহরণ: প্রতিটি কুয়েরিতে lower(trim(first_name || ' ' || last_name)) বারবার লেখার বদলে full_name_key একবার জেনারেট করে তার উপর ফিল্টার করুন।\n\n### মানুষ কিভাবে সোর্ট করে তার সাথে মিলিয়ে সোর্ট কী\n\nসোর্টিং হচ্ছে যেখানে গণিত করা ফিল্ড দ্রুতই সুবিধা দেয়, কারণ সোর্টিং PostgreSQL-কে অনেক রো-তে এক্সপ্রেশন মূল্যায়ন করাতে বাধ্য করে।\n\nসাধারণ সোর্ট কী-তে থাকে একটি নম্বর র্যাংক (প্ল্যান টিয়ারকে 1,2,3 ম্যাপ করা), একটি “সর্বশেষ কার্যকলাপ” টাইমস্ট্যাম্প (যেমন দুই টাইমস্ট্যাম্পের max), বা টেক্সট হিসেবে সঠিকভাবে সাজানো প্যাড করা কোড।\n\nযখন সোর্ট কীটি একটি সাধারণ ইনডেক্স করা কলাম হয়, ORDER BY অনেক সস্তা হয়ে যায়।\n\n### দ্রুত ফিল্টারের জন্য ডেরাইভেড ফ্ল্যাগ\n\nঅ্যাডমিন ব্যবহারকারীরা “Overdue” বা “High value” ধরার চেকবক্স খুব পছন্দ করে। যদি লজিকটি স্থিতিশীল এবং কেবল রো-ডেটার উপর নির্ভর করে তাহলে এগুলো generated column হিসেবে ভাল কাজ করে।\n\nউদাহরণস্বরূপ, যদি কাস্টমার তালিকায় “Has unread messages” এবং “Is overdue” লাগে, তাহলে একটি generated has_unread boolean (যা unread_count > 0 থেকে আসে) এবং is_overdue (যা due_date < now() এবং paid_at is null থেকে নির্ধারিত) UI ফিল্টারকে সরল শর্তে বদলে দেয়।\n\n## generated columns, ইনডেক্স ও অন্যান্য বিকল্পের মধ্যে নির্বাচন\n\nঅ্যাডমিন স্ক্রিনগুলোকে তিনটি জিনিস লাগে: দ্রুত ফিল্টারিং, দ্রুত সোর্টিং, এবং এমন SQL যা মাস পরে পড়তে সহজ থাকে। বাস্তব সিদ্ধান্ত হচ্ছে হিসাবটি কোথায় রাখা উচিত: টেবিলে, ইনডেক্সে, ভিউতে, না কি অ্যাপ কোডে।\n\nGenerated columns উপযোগী যখন আপনি চান যে মানটি একটি বাস্তব কলামের মতো আচরণ করুক: রেফার করা সহজ, SELECT-এ দেখা যায়, এবং নতুন ফিল্টার যোগ করার সময় ভুলে না যাওয়া যায়। এগুলো সাধারণ ইনডেক্সের সঙ্গে ভালো বোঝাপড়া করে।\n\nExpression indexes তাড়াতাড়ি যোগ করা সহজ কারণ টেবিল পরিবর্তন করতে হয় না। যদি আপনি কেবল গতি চান এবং গোলমেল SQL মানতে পারেন, একটি expression index প্রায়ই যথেষ্ট। খারাপ দিক হলো পড়তে অস্বাচ্ছিক এবং প্ল্যানারকে আপনার এক্সপ্রেশনের সঠিক মিল খুঁজে পেতে হবে।\n\nViews উপকারী যখন আপনি চাইলে একটি শেয়ার্ড ডেটা-আকৃতি, বিশেষত যদি অ্যাডমিন তালিকায় অনেক টেবিল জয়েন করে। কিন্তু জটিল ভিউ প্রচলিত ব্যয়বহুল কাজকে লুকিয়ে রাখতে পারে এবং ডিবাগ করার জন্য আরেকটি জায়গা যোগ করে।\n\nTriggers একটি সাধারণ কলাম সিঙ্ক রাখতে পারে, কিন্তু এগুলো মোবাইল পাটাতনের মতো। তাতে বাল্ক আপডেট ধীর হতে পারে এবং ট্রাবলশুট করার সময় সহজেই নজরে না আসতে পারে।\n\nকখনো কখনো সবচেয়ে ভালো অপশন হলো অ্যাপ দ্বারা ভরা একটি সাধারণ কলাম। যদি ব্যবহারকারী এটাকে সম্পাদনা করে, অথবা সূত্রটি বারবার ব্যবসায়িক সিদ্ধান্ত পরিবর্তিত হয় (শুধু রো ডেটা নয়), তাহলে এটি স্পষ্ট রাখা ভালো।\n\nএকটি দ্রুত সিদ্ধান্তের উপায়:\n\n- পড়তে সহজ কুয়েরি ও কেবল রো ডেটার উপর ভিত্তি করে স্থিতিশীল সূত্র চান? ব্যবহার করুন generated column।\n- একটি নির্দিষ্ট ফিল্টারের জন্য গতি চান এবং গোলমেল SQL মানতে পারেন? ব্যবহার করুন expression index।\n- অনেক জায়গায় একই জয়েন করা রিপোর্ট-আকৃতি চান? ভিউ বিবেচনা করুন।\n- ক্রস-টেবিল লজিক বা সাইড-এফেক্ট দরকার? আগে অ্যাপ লজিক, পরে ট্রিগার বিবেচনা করুন।\n\n## ধাপে ধাপে: একটি generated column যোগ করা এবং কুয়েরিতে ব্যবহার করা\n\nএকটা ধীর অ্যাডমিন লিস্ট কুয়েরি দিয়ে শুরু করুন যা UI-তে আপনি অনুভব করতে পারেন। স্ক্রিনে সবচেয়ে বেশি ব্যবহৃত ফিল্টার এবং সোর্টগুলো নোট করুন। প্রথমে সেই একক কুয়েরিটা উন্নত করুন।\n\nএকটি গণিত করা ফিল্ড বেছে নিন যা বারবার কাজ কমায়, এবং snake_case-এ স্পষ্টভাবে নাম দিন যেন অন্যরা একবার দেখে ধারণা করতে পারে সেটি কী ধরে রাখে।\n\n### 1) GENERATED কলাম যোগ করুন (STORED)\n\nsql\nALTER TABLE customers\nADD COLUMN full_name_key text\nGENERATED ALWAYS AS (\n lower(concat_ws(' ', last_name, first_name))\n) STORED;\n\n\nইনডেক্স যোগ করার আগে বাস্তব রোতে যাচাই করুন:\n\nsql\nSELECT id, first_name, last_name, full_name_key\nFROM customers\nORDER BY id DESC\nLIMIT 5;\n\n\nআউটপুট ভুল হলে এখনই এক্সপ্রেশন ঠিক করুন। STORED মানে PostgreSQL প্রতিটি ইনসার্ট ও আপডেটে এটি আপডেট রাখবে।\n\n### 2) আপনার অ্যাডমিন স্ক্রিন মিলে এমন ইনডেক্স যোগ করুন\n\nআপনার অ্যাডমিন স্ক্রিন যদি স্থিতি অনুযায়ী ফিল্টার করে এবং নাম অনুযায়ী সোর্ট করে, সেই প্যাটার্ন মেলানো ইনডেক্স তৈরি করুন:\n\nsql\nCREATE INDEX customers_status_full_name_key_idx\nON customers (status, full_name_key);\n\n\n### 3) অ্যাডমিন কুয়েরি আপডেট করে নতুন কলাম ব্যবহার করুন\n\nআগে হয়তো আপনার ORDER BY গোলমেলে ছিল। পরে এটি পরিষ্কার হয়ে যায়:\n\nsql\nSELECT id, status, first_name, last_name\nFROM customers\nWHERE status = 'active'\nORDER BY full_name_key ASC\nLIMIT 50 OFFSET 0;\n\n\nপ্রতিদিন মানুষ যেগুলো ফিল্টার এবং সোর্ট করে সেগুলোর জন্য generated columns ব্যবহার করুন, বিরল স্ক্রিনের জন্য নয়।\n\n## বাস্তবসম্মত ইনডেক্সিং প্যাটার্ন যা বাস্তব অ্যাডমিন স্ক্রিন মিলায়\n\nঅ্যাডমিন স্ক্রিনগুলো কয়েকটি আচরণ বারবার করে: কয়েকটি ফিল্টার কলাম দিয়ে ফিল্টার, একটি কলাম দিয়ে সোর্ট, এবং প্যাজিনেশন। সর্বোত্তম সেটআপ সাধারণত “সবকিছু ইনডেক্স করা” নয়। বরং এটা হলো “অধিকাংশ সাধারণ কুয়েরির সঠিক আকৃতি ইনডেক্স করা।”\n\nএকটি ব্যবহারিক নিয়ম: সবচেয়ে সাধারণ ফিল্টার কলামগুলো প্রথমে রাখুন, এবং সবচেয়ে সাধারণ সোর্ট কলামকে শেষে রাখুন। আপনি যদি মাল্টি-টেন্যান্ট হন, workspace_id (বা অনুরূপ) প্রায়ই প্রথম আসে: (workspace_id, status, created_at)।\n\nটেক্সট সার্চ নিজেই একটি সমস্যা। অনেক সার্চ বক্স শেষ পর্যন্ত ILIKE '%term%' হয়ে যায়, যা সাধারণ btree ইনডেক্স দিয়ে দ্রুত করা কঠিন। একটি সহায়ক প্যাটার্ন হচ্ছে একটি নরমালাইজড হেল্পার কলামে সার্চ করা (ছোট হাতের, ট্রিম করা, হয়তো একত্রিত)। যদি UI প্রিফিক্স সার্চ (term%) ব্যবহার করতে পারে, তাহলে সেই নরমালাইজড কলামের উপর btree ইনডেক্স সাহায্য করবে। যদি এটি কনটেইনস সার্চ (%term%) হতে হয়, UI আচরণ টাইটেন করা বা ছোট সাবসেট-এ সার্চ সীমাবদ্ধ করার কথা ভাবুন।\n\nইনডেক্স যোগ করার আগে সিলেক্টিভিটি চেক করুন। যদি 95% রো একই ভ্যালু শেয়ার করে (যেমন status = 'active'), ওই কলাম একা ইনডেক্স করা খুব সাহায্য করবে না। এটি আরও সিলেক্টিভ কলামের সঙ্গে পেয়ার করুন, অথবা মাইনোরিটি কেসের জন্য পারশিয়াল ইনডেক্স ব্যবহার করুন।\n\n## বাস্তব উদাহরণ: একটি দ্রুত থাকা কাস্টমার অ্যাডমিন লিস্ট\n\nএকটি সাধারণ কাস্টমার অ্যাডমিন পেজ কল্পনা করুন: একটি সার্চ বক্স, কয়েকটা ফিল্টার (inactive, balance range), এবং একটি sortable “Last seen” কলাম। সময়ের সাথে এটি গোলমেলে SQL হয়ে যায়: LOWER(), TRIM(), COALESCE(), তারিখ গণনা, এবং CASE ব্লকগুলো স্ক্রিন জুড়ে বারবার।\n\nএকটি পথ হলো এই পুনরাবৃত্ত এক্সপ্রেশনগুলো generated columns-এ ঠেলে দেয়া যাতে এটি দ্রুত ও পড়তে সহজ থাকে।\n\n### টেবিল ও generated কলামগুলো\n\nধরি একটি customers টেবিল আছে যার মধ্যে name, email, last_seen, এবং balance রয়েছে। তিনটি গণিত করা ফিল্ড যোগ করুন:\n\n- search_key: সাধারণ সার্চের জন্য নরমালাইজ করা টেক্সট\n- is_inactive: একটি বুলিয়ান যাতে ডেট লজিক বারবার লেখা লাগে না\n- balance_bucket: দ্রুত সেগমেন্টেশনের জন্য লেবেল\n\nsql\nALTER TABLE customers\n ADD COLUMN search_key text\n GENERATED ALWAYS AS (\n lower(trim(coalesce(name, ''))) || ' ' || lower(trim(coalesce(email, '')))\n ) STORED,\n ADD COLUMN is_inactive boolean\n GENERATED ALWAYS AS (\n last_seen IS NULL OR last_seen < (now() - interval '90 days')\n ) STORED,\n ADD COLUMN balance_bucket text\n GENERATED ALWAYS AS (\n CASE\n WHEN balance < 0 THEN 'negative'\n WHEN balance < 100 THEN '0-99'\n WHEN balance < 500 THEN '100-499'\n ELSE '500+'\n END\n ) STORED;\n\n\nএখন অ্যাডমিন কুয়েরি UI-এর মতো পড়বে।\n\n### পাঠযোগ্য ফিল্টার + সোর্টিং\n\n“Inactive customers, newest activity first” হয়ে যায়:\n\nsql\nSELECT id, name, email, last_seen, balance\nFROM customers\nWHERE is_inactive = true\nORDER BY last_seen DESC NULLS LAST\nLIMIT 50;\n\n\nএবং একটি বেসিক সার্চ হয়ে যায়:\n\nsql\nSELECT id, name, email, last_seen, balance\nFROM customers\nWHERE search_key LIKE '%' || lower(trim($1)) || '%'\nORDER BY last_seen DESC NULLS LAST\nLIMIT 50;\n\n\nবাস্তিক জয় হলো সঙ্গতি। একই ফিল্ড বহু স্ক্রিনে লজিক পুনরাবৃত্তি ছাড়াই চলবে:\n\n- কাস্টমার লিস্ট সার্চ বক্স search_key ব্যবহার করবে\n- “Inactive customers” ট্যাব is_inactive ব্যবহার করবে\n- ব্যালান্স ফিল্টার চিপগুলো balance_bucket ব্যবহার করবে\n\n## সাধারণ ভুল ও ফাঁদ\n\nGenerated columns একটি সহজ জয়ের মতো দেখায়: টেবিলে গণিত রাখুন এবং কুয়েরি পরিষ্কার রাখুন। এগুলো কেবল তখনি সাহায্য করে যখন সেগুলো স্ক্রিন কিভাবে ফিল্টার ও সোর্ট করে তার সাথে মিলে, এবং আপনি সঠিক ইনডেক্স যোগ করেন।\n\nসবচেয়ে সাধারণ ভুলগুলো:\n\n- ইনডেক্স ছাড়াই এটি দ্রুত হবে ভাবা। গণিত করা মানটি বড় পরিসরে ফিল্টার বা সোর্ট করার জন্য ইনডেক্স চাই।\n- এক কলামে খুব বেশি লজিক প্যাক করা। যদি একটি generated column একটি ক্ষুদ্র প্রোগ্রামের মতো হয়ে যায়, মানুষ সেটার ওপর নির্ভর করতে বন্ধ করবে। সংক্ষিপ্ত রাখুন এবং স্পষ্ট নাম দিন।\n- অ-ইমিউটেবল ফাংশন ব্যবহার করা। PostgreSQL স্টোরড generated column-এর এক্সপ্রেশন ইমিউটেবল হওয়া চাই — now() বা random() মত ফাংশন সমস্যা করে বা অনুমোদিত নাও হতে পারে।\n- রাইট কস্ট উপেক্ষা করা। ইনসার্ট ও আপডেটগুলোকে গণিত করা মানটি বজায় রাখতে হবে। যদি ইম্পোর্ট বা ইন্টিগ্রেশন ধীর হয়ে যায় তবে দ্রুত রিডের মূল্য কমে যায়।\n- কাছাকাছি ডুপ্লিকেট তৈরি করা। এক বা দুই মানকরণ প্যাটার্ন স্ট্যান্ডার্ডাইজ করুন (যেমন এক নরমালাইজড কী) পাঁচটি অনুরূপ কলাম জমা করার বদলে।\n\nযদি আপনার অ্যাডমিন লিস্ট ILIKE '%ann%' মতো কনটেইনস সার্চ করে, একটি generated column একা তা রক্ষা করবে না। কিন্তু দৈনন্দিন “ফিল্টার ও সোর্ট” কাজের জন্য generated columns এবং সঠিক ইনডেক্স সাধারণত পারফরম্যান্সকে অনেক বেশি পূর্বানুমেয় করে তোলে।\n\n## শিপ করার আগে দ্রুত চেকলিস্ট\n\nপরিবর্তন পাঠানোর আগে নিশ্চিত করুন গণিত মান, কুয়েরি, এবং ইনডেক্স একে অপরের সঙ্গে মিলছে:\n\n- সূত্রটি স্থিতিশীল এবং এক বাক্যে সহজে বুঝানো যায়।\n- আপনার কুয়েরি আসলে generated column WHERE এবং/অথবা ORDER BY-এ ব্যবহার করছে।\n- ইনডেক্স বাস্তবে ব্যবহারের সাথে মেলে, কখনো একবারের টেস্ট নয়।\n- পুরনো লজিকের সাথে ফলাফল তুলনা করেছেন কিনা (NULLs, ফাঁকা স্ট্রিং, অদ্ভুত স্পেসিং, মিক্সড কেস)।\n- টেবিল ব্যস্ত হলে রাইট পারফরম্যান্স টেস্ট করেছেন কিনা (ইম্পোর্ট, ব্যাকগ্রাউন্ড আপডেট, ইন্টিগ্রেশন)।\n\n## পরবর্তী ধাপ: আপনার অ্যাডমিন স্ক্রিনে এটি প্রয়োগ করা\n\nএকটা ছোট, উচ্চ-প্রভাব শুরু বেছে নিন: ২-৩ টি অ্যাডমিন স্ক্রিন যেগুলো মানুষ সারা দিন খুলে রাখে (অর্ডার, কাস্টমার, টিকেট)। কী ধীর লাগছে নোট করুন (একটি তারিখ রেঞ্জ ফিল্টার, “শেষ কার্যকলাপ” অনুযায়ী সোর্ট করা, মিলিত নাম দিয়ে সার্চ, স্ট্যাটাস লেবেল দ্বারা ফিল্টার)। তারপর এমন একটি সংক্ষিপ্ত সেটের গণিত করা ফিল্ড স্ট্যান্ডার্ডাইজ করুন যেগুলো আপনি স্ক্রিন জুড়ে পুনরায় ব্যবহার করতে পারবেন।\n\nএকটি রোলআউট প্ল্যান যা পরিমাপ করা সহজ এবং উল্টে ফেলা সহজ:\n\n- স্পষ্ট নাম দিয়ে generated column(s) যুক্ত করুন।\n- যদি আপনি বিদ্যমান লজিক রিপ্লেস করে থাকেন, কিছুক্ষণ পুরনো ও নতুন পাশে পাশে চালান।\n- মেইন ফিল্টার বা সোর্ট মেলানো ইনডেক্স যোগ করুন।\n- স্ক্রিনের কুয়েরি নতুন কলাম ব্যবহার করতে স্যুইচ করুন।\n- আগের ও পরে মাপুন (কুয়েরি টাইম এবং স্ক্যান করা রো), তারপর পুরনো ওয়ার্কঅ্যারাউন্ড সরান।\n\nআপনি যদি AppMaster-এ (AppMaster) আভ্যন্তরীণ অ্যাডমিন টুলস বানান, এই গণিত করা ফিল্ডগুলো একটি শেয়ার্ড ডেটা মডেলে সুন্দরভাবে ফিট করে: ডাটাবেস নিয়ম বহন করে, এবং আপনার UI ফিল্টারগুলো সরাসরি একটি সরল ফিল্ড নামের দিকে পয়েন্ট করে, কুয়েরি এক্সপ্রেশন বারবার না লেখাই।
প্রশ্নোত্তর
Generated columns help when you keep repeating the same expression in WHERE or ORDER BY, like normalizing names, mapping statuses, or building a sorting key. They’re especially useful for admin lists that are opened all day and need predictable filtering and sorting.
A stored generated column is computed on insert or update and saved like a normal column, so reads can be fast and indexable. An expression index stores the result in the index without adding a new table column, but your queries still need to use the exact expression for the planner to match it.
No, not by itself. A generated column mainly makes the query simpler and makes indexing a computed value straightforward, but you still need an index that matches your common filters and sorts if you want real speedups at scale.
Usually it’s a field you filter or sort on constantly: a normalized search key, a “full name” sort key, a derived boolean like is_overdue, or a ranking number that matches how people expect results to sort. Pick one value that removes repeated work from many queries, not a one-off calculation.
Start with the most common filter columns, then put the main sort key last, like (workspace_id, status, full_name_key) if that matches the screen. This lets PostgreSQL filter quickly and then return rows already ordered without extra work.
Not very. A generated column can normalize text so behavior is consistent, but ILIKE '%term%' still tends to be slow with basic btree indexes on large tables. If performance matters, prefer prefix-style search where you can, reduce the searched dataset with other filters, or adjust the UI behavior for big tables.
Stored generated columns have to be based on immutable expressions, so functions like now() typically aren’t allowed and would also be conceptually wrong because the value would go stale. For time-based flags like “inactive for 90 days,” consider a normal column maintained by a job, or compute it at query time if it’s not heavily used.
Yes, but plan it like a real migration. Changing the expression means updating the schema and recomputing values for existing rows, which can take time and add write load, so do it in a controlled deployment window if the table is large.
Yes. The database has to compute and store the value on every insert and update, so heavy write workloads (imports, sync jobs) can slow down if you add too many generated fields or complex expressions. Keep expressions short, add only what you use, and measure write performance on busy tables.
Add a generated column, validate a few real rows, then add the index that matches the screen’s main filter and sort. Update the admin query to use the new column directly, and compare query time and rows scanned before and after to confirm the change helped.


