آموزش تابع FIRST_VALUE در SQL Server با ۱۰ مثال عملی | آموزش جامع و مثال عملی

آموزش تابع FIRST_VALUE در SQL Server با ۱۰ مثال عملی

توسط admin | گروه SQL Server | 1405/04/29

نظرات 0

آموزش جامع تابع FIRST_VALUE در SQL Server با ۱۰ مثال عملی

مقدمه

تابع FIRST_VALUE یکی از ابزارهای مهم تحلیل پنجره‌ای در Microsoft SQL Server است و برای مقایسه هر ردیف با نخستین مقدار یک گروه، دوره یا بازه تحلیلی به کار می‌رود. این مقاله از نحو پایه شروع می‌کند و سپس رفتار مرزی، NULL، ترتیب قطعی، سناریوی سازمانی و بهینه‌سازی را با Queryهای مستقل بررسی می‌کند.

مزیت تابع پنجره‌ای این است که جزئیات هر ردیف حفظ می‌شود و هم‌زمان محاسبه‌ای مبتنی بر ردیف‌های مرتبط در دسترس قرار می‌گیرد. این ویژگی گزارش‌سازی را از Self Joinهای شکننده، Cursor و پردازش تکراری در لایه برنامه بی‌نیاز می‌کند، مشروط بر اینکه ترتیب و پارتیشن درست تعریف شوند.

برای مشاهده نقشه کامل این خانواده، راهنمای جامع توابع تحلیلی و پنجره‌ای SQL Server را نیز مطالعه کنید.

تعریف و منطق تابع FIRST_VALUE

FIRST_VALUE برای مقایسه هر ردیف با نخستین مقدار یک گروه، دوره یا بازه تحلیلی استفاده می‌شود. پنجره تحلیل با OVER تعریف می‌گردد و موتور SQL Server نتیجه را بدون حذف ردیف‌های اصلی محاسبه می‌کند. این رفتار تفاوت بنیادی آن با GROUP BY است.

در طراحی حرفه‌ای باید سؤال کسب‌وکار به سه جزء تبدیل شود: مرز گروه با PARTITION BY، ترتیب منطقی با ORDER BY و در صورت نیاز Frame. سپس نوع خروجی، رفتار NULL و مقادیر هم‌رتبه بررسی می‌شود. MIN کوچک‌ترین مقدار را می‌دهد؛ FIRST_VALUE مقدار اولین ردیف طبق ORDER BY را می‌دهد.

نحو استاندارد

FIRST_VALUE ( scalar_expression ) [ IGNORE NULLS | RESPECT NULLS ] OVER ( [ PARTITION BY ... ] ORDER BY ... [ ROWS frame ] )

پارامترها و اجزای مهم

  • scalar_expression یا عبارت مرتب‌سازی باید نوع داده مناسب و قابل پیش‌بینی داشته باشد.
  • PARTITION BY اختیاری است و در نبود آن، تمام ردیف‌های ورودی یک پارتیشن هستند.
  • ORDER BY ترتیب محاسبه را تعیین می‌کند و بهتر است با یک tie-breaker یکتا کامل شود.
  • Frame در توابع وابسته به مرز پنجره باید صریح نوشته شود؛ رفتار پیش‌فرض همیشه معادل کل پارتیشن نیست.
  • NULL باید با سیاست روشن مدیریت شود؛ صفر، رشته خالی و NULL معنای یکسان ندارند.

نوع خروجی

نوع خروجی FIRST_VALUE: هم‌نوع با scalar_expression. در لایه گزارش، تبدیل به decimal یا متن فقط برای نمایش انجام شود و مقدار خام برای محاسبات بعدی نگه داشته شود.

نکته کلیدی: اگر ترتیب و tie-breaker دقیق نباشد، اولین مقدار از دید کسب‌وکار الزاماً ثابت نیست.

مثال‌های عملی مستقل و قابل اجرا

مثال 1: مقدار مرجع در کل توالی

هر ردیف با اولین مبلغ بر پایه ترتیب زمانی مقایسه می‌شود.

WITH Sales AS
(
    SELECT *
    FROM (VALUES
        (1, N'فروش', CAST('2026-01-01' AS date), 100),
        (2, N'فروش', CAST('2026-02-01' AS date), 140),
        (3, N'فروش', CAST('2026-03-01' AS date), 120),
        (4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
        (5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
    ) AS V(Id, Department, SaleDate, Amount)
)
SELECT SaleDate,Amount,
       FIRST_VALUE(Amount) OVER (ORDER BY SaleDate,Id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS BoundaryAmount
FROM Sales WHERE Department=N'فروش' ORDER BY SaleDate,Id;
تاریخمبلغمقدار مرزی
2026-02-01140100

نکته کاربردی: Frame صریح، قصد نویسنده Query را برای نگهداری آینده روشن می‌کند.

مثال 2: محاسبه مستقل برای هر دپارتمان

PARTITION BY مرجع آغاز یا پایان هر واحد را مستقل نگه می‌دارد.

WITH Sales AS
(
    SELECT *
    FROM (VALUES
        (1, N'فروش', CAST('2026-01-01' AS date), 100),
        (2, N'فروش', CAST('2026-02-01' AS date), 140),
        (3, N'فروش', CAST('2026-03-01' AS date), 120),
        (4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
        (5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
    ) AS V(Id, Department, SaleDate, Amount)
)
SELECT Department,SaleDate,Amount,
       FIRST_VALUE(Amount) OVER (PARTITION BY Department ORDER BY SaleDate,Id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS BoundaryAmount
FROM Sales ORDER BY Department,SaleDate,Id;
واحدمبلغمرجع
فروش140100
پشتیبانی11080

نکته کاربردی: مرز پارتیشن از آمیخته شدن واحدهای کسب‌وکار جلوگیری می‌کند.

مثال 3: تعریف صریح Frame برای FIRST_VALUE

Frame پنجره بر معنای مقدار مرزی اثر مستقیم دارد و باید همراه ORDER BY خوانده شود.

WITH Sales AS
(
    SELECT *
    FROM (VALUES
        (1, N'فروش', CAST('2026-01-01' AS date), 100),
        (2, N'فروش', CAST('2026-02-01' AS date), 140),
        (3, N'فروش', CAST('2026-03-01' AS date), 120),
        (4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
        (5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
    ) AS V(Id, Department, SaleDate, Amount)
)
SELECT SaleDate,Amount,
       FIRST_VALUE(Amount) OVER (ORDER BY SaleDate,Id) AS DefaultFrame,
       FIRST_VALUE(Amount) OVER (ORDER BY SaleDate,Id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS ExplicitFrame
FROM Sales WHERE Department=N'فروش' ORDER BY SaleDate,Id;
مبلغپیش‌فرضصریح
140100100

نکته کاربردی: برای LAST_VALUE تفاوت حیاتی است؛ برای FIRST_VALUE نیز صراحت Frame از برداشت اشتباه جلوگیری می‌کند.

مثال 4: محاسبه فاصله از نقطه مرجع

تفاضل مبلغ جاری با مقدار مرزی، رشد تجمعی یا فاصله تا هدف نهایی را می‌سازد.

WITH Sales AS
(
    SELECT *
    FROM (VALUES
        (1, N'فروش', CAST('2026-01-01' AS date), 100),
        (2, N'فروش', CAST('2026-02-01' AS date), 140),
        (3, N'فروش', CAST('2026-03-01' AS date), 120),
        (4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
        (5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
    ) AS V(Id, Department, SaleDate, Amount)
)
SELECT SaleDate,Amount,
       Amount-FIRST_VALUE(Amount) OVER (ORDER BY SaleDate,Id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS DifferenceFromBoundary
FROM Sales WHERE Department=N'فروش' ORDER BY SaleDate,Id;
تاریخمبلغفاصله
2026-02-0114040

نکته کاربردی: علامت تفاضل را مطابق سؤال کسب‌وکار انتخاب کنید؛ فاصله از آغاز با فاصله تا پایان متفاوت است.

مثال 5: انتخاب ردیف پس از محاسبه پنجره

برای فیلتر نتیجه تابع، محاسبه در CTE انجام و شرط در Query بیرونی اعمال می‌شود.

WITH Sales AS
(
    SELECT *
    FROM (VALUES
        (1, N'فروش', CAST('2026-01-01' AS date), 100),
        (2, N'فروش', CAST('2026-02-01' AS date), 140),
        (3, N'فروش', CAST('2026-03-01' AS date), 120),
        (4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
        (5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
    ) AS V(Id, Department, SaleDate, Amount)
)
,W AS (SELECT *,FIRST_VALUE(Amount) OVER(PARTITION BY Department ORDER BY SaleDate,Id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS B FROM Sales)
SELECT Id,Department,Amount,B FROM W WHERE Amount<>B ORDER BY Department,Id;
Idمبلغمرجع
2140100

نکته کاربردی: فیلتر زودهنگام می‌تواند پارتیشن را تغییر دهد؛ محل اعمال WHERE بخشی از تعریف مسئله است.

مثال 6: مدیریت NULL در نسخه 2022

IGNORE NULLS نخستین یا آخرین مقدار غیرNULL را در پنجره انتخاب می‌کند.

WITH D AS (SELECT * FROM (VALUES (1,CAST(NULL AS int)),(2,20),(3,NULL),(4,40))V(Id,Value))
SELECT Id,Value,
       FIRST_VALUE(Value) IGNORE NULLS OVER(ORDER BY Id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS NonNullBoundary
FROM D ORDER BY Id;
Idمقدارمرز غیرNULL
3NULL20

نکته کاربردی: در نسخه‌های قدیمی‌تر باید از الگوهای جایگزین مانند مرتب‌سازی شرطی یا زیرپرس‌وجو استفاده شود.

مثال 7: ترتیب تجاری متفاوت از کمینه و بیشینه

FIRST_VALUE بر اساس ORDER BY عمل می‌کند و الزاماً MIN یا MAX نیست.

WITH Prices AS (SELECT * FROM (VALUES (1,3,900),(2,1,1200),(3,2,1000))V(Id,Priority,Price))
SELECT Id,Priority,Price,
       FIRST_VALUE(Price) OVER(ORDER BY Priority,Id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS BusinessBoundary,
       MIN(Price) OVER() AS MinimumPrice
FROM Prices ORDER BY Priority,Id;
اولویتقیمتمرز تجاری
39001200

نکته کاربردی: انتخاب ORDER BY باید دقیقاً تعریف ترتیب کسب‌وکار باشد، نه صرفاً ساده‌ترین ستون.

مثال 8: مقایسه وضعیت سفارش با مرز فرایند

در فرایند سفارش، همه رخدادها را می‌توان با وضعیت ابتدایی یا نهایی همان سفارش مقایسه کرد.

WITH H AS (SELECT * FROM (VALUES (10,1,N'ثبت'),(10,2,N'پرداخت'),(10,3,N'ارسال'),(20,1,N'ثبت'))V(OrderId,StepNo,State))
SELECT OrderId,StepNo,State,
       FIRST_VALUE(State) OVER(PARTITION BY OrderId ORDER BY StepNo ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS BoundaryState
FROM H ORDER BY OrderId,StepNo;
سفارشمرحلهوضعیت مرزی
102ثبت

نکته کاربردی: این الگو برای Audit Trail مفید است، اما تاریخچه باید ترتیب یکتا و قابل اعتماد داشته باشد.

مثال 9: اصلاح TOP بدون پارتیشن

TOP یا زیرپرس‌وجوی مستقل معمولاً یک مقدار سراسری می‌دهد؛ تابع پنجره‌ای مرز هر گروه را حفظ می‌کند.

WITH Sales AS
(
    SELECT *
    FROM (VALUES
        (1, N'فروش', CAST('2026-01-01' AS date), 100),
        (2, N'فروش', CAST('2026-02-01' AS date), 140),
        (3, N'فروش', CAST('2026-03-01' AS date), 120),
        (4, N'پشتیبانی', CAST('2026-01-01' AS date), 80),
        (5, N'پشتیبانی', CAST('2026-02-01' AS date), 110)
    ) AS V(Id, Department, SaleDate, Amount)
)
SELECT Department,SaleDate,Amount,
       FIRST_VALUE(Amount) OVER(PARTITION BY Department ORDER BY SaleDate,Id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CorrectPerGroup
FROM Sales ORDER BY Department,SaleDate,Id;
واحدمرجع صحیح
فروش100
پشتیبانی80

نکته کاربردی: وقتی تعداد گروه‌ها زیاد است، بیان پنجره‌ای هم کوتاه‌تر و هم قابل بهینه‌سازی‌تر از زیرپرس‌وجوی همبسته است.

مثال 10: ایندکس و کنترل Sort

یک ایندکس مطابق پارتیشن و ترتیب می‌تواند نیاز به مرتب‌سازی جداگانه را کاهش دهد.

CREATE TABLE #H(GroupId int,EventDate date,EventId int,Value int);
INSERT #H VALUES(1,'2026-01-01',1,10),(1,'2026-02-01',2,20),(1,'2026-03-01',3,15);
CREATE INDEX IX_H_Window ON #H(GroupId,EventDate,EventId) INCLUDE(Value);
SELECT *,FIRST_VALUE(Value) OVER(PARTITION BY GroupId ORDER BY EventDate,EventId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS BoundaryValue FROM #H;
DROP TABLE #H;
عملگرانتظار
Sortکاهش احتمالی با ایندکس هم‌راستا

نکته کاربردی: به حذف Sort اکتفا نکنید؛ Logical Reads، CPU، Memory Grant و Spill نیز باید اندازه‌گیری شوند.

خطاهای رایج و روش اصلاح

  • اگر ترتیب و tie-breaker دقیق نباشد، اولین مقدار از دید کسب‌وکار الزاماً ثابت نیست.
  • استفاده از ORDER BY غیرقطعی؛ یک کلید یکتا به انتهای ترتیب اضافه کنید.
  • اعمال WHERE در سطح اشتباه؛ ابتدا مشخص کنید فیلتر باید جمعیت پنجره را عوض کند یا فقط خروجی را محدود نماید.
  • تبدیل نوع ضمنی در عبارت یا مقدار پیش‌فرض؛ نوع‌ها را صریح و سازگار تعریف کنید.
  • فرض اینکه خروجی تابع همیشه deterministic است؛ مستندات و ترتیب داده را برای امکان تکرار نتیجه کنترل کنید.

ملاحظات کارایی و تحلیل Execution Plan

Window Spool و Sort را در Actual Execution Plan بررسی کنید و ترتیب ایندکس را با پنجره هماهنگ سازید.

ابتدا با SET STATISTICS IO, TIME ON خط مبنا بگیرید و Actual Execution Plan را ذخیره کنید. وجود Sort بزرگ، Memory Grant بیش از نیاز یا هشدار Spill به tempdb نشانه‌ای است که ترتیب داده، برآورد Cardinality یا ظرفیت حافظه باید بررسی شود.

ایندکس پیشنهادی معمولاً با ستون‌های فیلتر برابری و PARTITION BY آغاز می‌شود، سپس ستون‌های ORDER BY و tie-breaker می‌آیند و ستون خروجی در INCLUDE قرار می‌گیرد. این یک نسخه عمومی است؛ ترتیب نهایی باید با Query واقعی، Selectivity و هزینه نگهداری DML سنجیده شود.

چند تابع پنجره‌ای با Window Specification یکسان را در یک SELECT بنویسید تا Optimizer امکان استفاده مشترک از ترتیب را داشته باشد. تفاوت کوچک در ترتیب صعودی، نزولی یا Frame می‌تواند عملگر جداگانه بسازد؛ پس طرح اجرا را پس از هر تغییر مقایسه کنید.

فیلتر دوره زمانی و ستون‌های غیرضروری را در جایی اعمال کنید که معنای تحلیل حفظ شود. کاهش عرض و تعداد ردیف ورودی، مصرف حافظه و I/O را کم می‌کند، ولی بهینه‌سازی نباید جمعیت آماری مورد نیاز را ناخواسته حذف کند.

بهترین روش‌ها

  1. تعریف کسب‌وکار را پیش از نوشتن Query به پارتیشن، ترتیب و Frame تبدیل کنید.
  2. در ORDER BY از کلید یکتای پایدار برای شکستن tie استفاده کنید.
  3. NULL، پارتیشن تک‌ردیفی، مقادیر مساوی و مرزهای ابتدا و انتها را آزمون کنید.
  4. نوع داده خروجی و گرد کردن را مستند کنید و گرد کردن را تا لایه نمایش عقب بیندازید.
  5. Execution Plan، IO، CPU، Memory Grant و tempdb Spill را قبل و بعد از تغییر ثبت کنید.
  6. ایندکس را براساس بار کاری کامل طراحی کنید؛ بهبود SELECT نباید هزینه INSERT و UPDATE را نادیده بگیرد.

سؤالات متداول

۱. تابع FIRST_VALUE دقیقاً چه مسئله‌ای را حل می‌کند؟

این تابع برای مقایسه هر ردیف با نخستین مقدار یک گروه، دوره یا بازه تحلیلی طراحی شده است. مزیت اصلی آن حفظ ردیف‌های جزئی در کنار محاسبه تحلیلی است و برخلاف GROUP BY الزاماً تعداد ردیف‌ها را کاهش نمی‌دهد.

۲. اجزای OVER در FIRST_VALUE چه نقشی دارند؟

PARTITION BY مرز گروه منطقی را تعیین می‌کند و ORDER BY توالی تحلیل را می‌سازد. در توابع حساس به Frame، عبارت ROWS نیز دامنه ردیف‌های قابل مشاهده از هر ردیف را مشخص می‌کند.

۳. آیا استفاده از FIRST_VALUE هزینه توسعه گزارش را کم می‌کند؟

در بسیاری از گزارش‌ها حذف Self Join، Cursor یا کد میانی باعث Query کوتاه‌تر و نگهداری ساده‌تر می‌شود. برای برآورد تجاری باید حجم داده، SLA، دفعات اجرا و هزینه ایندکس نیز اندازه‌گیری شود.

۴. چه زمانی بازطراحی Queryهای قدیمی با FIRST_VALUE ارزش اقتصادی دارد؟

اگر گزارش پرتکرار، زمان‌بر یا مستعد خطای محاسباتی باشد، بازطراحی می‌تواند زمان پشتیبانی و مصرف منابع را کم کند. یک ارزیابی یا مشاوره SQL Server با خط مبنای قبل و بعد، تصمیم سرمایه‌گذاری را مستند می‌کند.

۵. تفاوت FIRST_VALUE با گزینه نزدیک آن چیست؟

MIN کوچک‌ترین مقدار را می‌دهد؛ FIRST_VALUE مقدار اولین ردیف طبق ORDER BY را می‌دهد. انتخاب نهایی باید براساس تعریف دقیق خروجی، رفتار tie، NULL و مرز پنجره انجام شود.

۶. برای پیاده‌سازی حرفه‌ای FIRST_VALUE در پروژه سازمانی چه خدمتی لازم است؟

ابتدا Query و Execution Plan واقعی بررسی، سپس ایندکس و آزمون صحت روی داده مرزی طراحی می‌شود. خدمات تحلیل، آموزش تیم یا بهینه‌سازی پروژه می‌تواند این مراحل را با معیار پذیرش روشن اجرا کند.

۷. رایج‌ترین خطا در FIRST_VALUE چیست؟

اگر ترتیب و tie-breaker دقیق نباشد، اولین مقدار از دید کسب‌وکار الزاماً ثابت نیست. علاوه بر آن، فیلتر کردن داده پیش از محاسبه می‌تواند جمعیت آماری یا همسایه‌های پنجره را ناخواسته تغییر دهد.

۸. چگونه Performance تابع FIRST_VALUE را بسنجیم؟

Window Spool و Sort را در Actual Execution Plan بررسی کنید و ترتیب ایندکس را با پنجره هماهنگ سازید. Actual Execution Plan، SET STATISTICS IO/TIME، Memory Grant، هشدار Spill و تعداد ردیف واقعی ابزارهای اصلی اندازه‌گیری هستند.

۹. بهترین روش نوشتن FIRST_VALUE چیست؟

ترتیب قطعی با tie-breaker یکتا، پارتیشن متناسب با منطق کسب‌وکار، تبدیل نوع صریح و آزمون NULL و مرزها را رعایت کنید. ابتدا صحت و سپس سرعت را بهینه سازید.

۱۰. FIRST_VALUE با کدام نسخه‌های SQL Server سازگار است؟

SQL Server 2012؛ گزینه IGNORE NULLS از SQL Server 2022. در Azure SQL نیز اصل قابلیت در دسترس است، اما Compatibility Level و اصلاحات تجمعی مرتبط با IGNORE NULLS یا Optimizer باید بررسی شود.

سؤالات مصاحبه و پاسخ کوتاه

چرا FIRST_VALUE یک تابع پنجره‌ای است؟

زیرا خروجی را با مشاهده مجموعه‌ای از ردیف‌های مرتبط می‌سازد، اما نتیجه را کنار هر ردیف نگه می‌دارد و مانند Aggregate معمولی گروه را به یک ردیف فرو نمی‌کاهد.

چرا ORDER BY باید قطعی باشد؟

اگر چند ردیف کلید ترتیب یکسان داشته باشند، موتور می‌تواند ترتیب داخلی متفاوتی انتخاب کند. افزودن کلید یکتا نتیجه را تکرارپذیر و آزمون‌پذیر می‌سازد.

تفاوت WHERE داخلی و خارجی چیست؟

WHERE داخلی جمعیت ورودی پنجره را کاهش می‌دهد؛ WHERE خارجی فقط خروجی محاسبه‌شده را فیلتر می‌کند. بنابراین این دو از نظر معنا معادل نیستند.

چه چیزی را در Execution Plan بررسی می‌کنید؟

Sort، Segment، Sequence Project یا Window Aggregate، Memory Grant، Spill به tempdb، برآورد ردیف و هم‌راستایی ایندکس با PARTITION و ORDER BY بررسی می‌شوند.

آزمون واحد مناسب چیست؟

پارتیشن خالی یا تک‌ردیفی، NULL، مقدار تکراری، مرز ابتدا و انتها، ترتیب هم‌رتبه و داده حجیم باید پوشش داده شوند تا هم صحت و هم پایداری سنجیده شود.

چک‌لیست نهایی

  • نحو FIRST_VALUE و Compatibility Level کنترل شده است.
  • پارتیشن دقیقاً مرز موجودیت کسب‌وکار است.
  • ترتیب با tie-breaker یکتا قطعی شده است.
  • Frame در صورت اثرگذاری صریح نوشته شده است.
  • حالت NULL، tie و پارتیشن کوچک آزمون شده است.
  • فیلتر داخلی و خارجی آگاهانه انتخاب شده‌اند.
  • طرح اجرا و آمار IO/TIME ثبت شده‌اند.
  • لینک مقاله مادر و نمونه‌ها پیش از انتشار کنترل شده‌اند.

جمع‌بندی

تابع FIRST_VALUE وقتی ارزشمند است که برای مقایسه هر ردیف با نخستین مقدار یک گروه، دوره یا بازه تحلیلی به یک بیان مجموعه‌محور، خوانا و قابل بهینه‌سازی نیاز داشته باشیم. صحت ترتیب و پارتیشن مهم‌تر از کوتاهی ظاهری Query است و سنجش کارایی باید با داده و Plan واقعی انجام شود.

برای مقایسه FIRST_VALUE با هفت تابع دیگر، به مقاله مادر توابع Analytic و Window در SQL Server بازگردید.

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

حرف 500 حداکثر

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620