Thử thách truy vấn: Tạo các thùng có kích thước đồng đều, dựa trên số đo không tính hàng


12

Tôi sẽ mô tả vấn đề về việc tải một số lượng xe tải cố định với các đơn đặt hàng, càng nhiều càng tốt.

Đầu vào:

@TruckCount - the number of empty trucks to fill

Một bộ:

OrderId, 
OrderDetailId, 
OrderDetailSize, 
TruckId (initially null)

Ordersbao gồm một hoặc nhiều OrderDetails.

Thách thức ở đây là chỉ định một TruckIdcho mỗi bản ghi.

Một đơn hàng không thể được phân chia trên các xe tải.

Xe tải nên được tải càng đều * càng tốt, được đo bằng sum(OrderDetailSize).

* Đồng đều: Đồng bằng nhỏ nhất có thể đạt được giữa xe tải ít tải nhất và xe tải nhiều nhất. Theo định nghĩa này, 1,2,3 được phân phối đồng đều hơn 1,1,4. Nếu nó giúp, giả vờ bạn là thuật toán thống kê, tạo biểu đồ chiều cao thậm chí.

Không có xem xét cho tải trọng xe tối đa. Đây là những chiếc xe tải đàn hồi ma thuật. Số lượng xe tải tuy nhiên là cố định.

Rõ ràng có một giải pháp lặp đi lặp lại - vòng tròn phân bổ đơn đặt hàng.

Nhưng nó có thể được thực hiện như logic dựa trên thiết lập?

Mối quan tâm chính của tôi là dành cho SQL Server 2014 trở lên. Nhưng các giải pháp dựa trên thiết lập cho các nền tảng khác cũng có thể thú vị.

Cảm giác này giống như lãnh thổ của Itzik Ben-Gan :)

Ứng dụng trong thế giới thực của tôi đang phân phối một khối lượng công việc xử lý vào một số nhóm để phù hợp với số lượng CPU logic. Do đó mỗi thùng không có kích thước tối đa. Thống kê cập nhật, cụ thể. Tôi chỉ nghĩ sẽ vui hơn khi trừu tượng hóa vấn đề thành xe tải như một cách đóng khung cho thử thách.

CREATE TABLE #OrderDetail (
OrderId int NOT NULL,
OrderDetailId int NOT NULL PRIMARY KEY,
OrderDetailSize tinyint NOT NULL,
TruckId tinyint NULL)

-- Sample Data

INSERT #OrderDetail (OrderId, OrderDetailId, OrderDetailSize)
VALUES
(1  ,100    ,75 ),
(2  ,101    ,5  ),
(2  ,102    ,5  ),
(2  ,103    ,5  ),
(2  ,104    ,5  ),
(2  ,105    ,5  ),
(3  ,106    ,100),
(4  ,107    ,1  ),
(5  ,108    ,11 ),
(6  ,109    ,21 ),
(7  ,110    ,49 ),
(8  ,111    ,25 ),
(8  ,112    ,25 ),
(9  ,113    ,40 ),
(10 ,114    ,49 ),
(11 ,115    ,10 ),
(11 ,116    ,10 ),
(12 ,117    ,15 ),
(13 ,118    ,18 ),
(14 ,119    ,26 )
--> YOUR SOLUTION HERE

-- After assigning Trucks, Measure delta between most and least loaded trucks.
-- Zero is perfect score, however the challenge is a set based solution that will scale, and produce good results, rather
-- than iterative solution that will produce perfect results by exploring every possibility.

SELECT max(TruckOrderDetailSize) - MIN(TruckOrderDetailSize) AS TruckMinMaxDelta
FROM 
(SELECT SUM(OrderDetailSize) AS TruckOrderDetailSize FROM #OrderDetail GROUP BY TruckId) AS Truck


DROP TABLE #OrderDetail

7
Đây có vẻ là vấn đề đóng gói bin cổ điển .
Dan Guzman

1
Hugo Kornelis cũng có một công việc tốt về nó.
Erik Darling

Tất cả các giá trị OrderDetailSize sẽ bằng nhau cho một OrderId nhất định hay đó chỉ là tỷ lệ chung trong dữ liệu mẫu của bạn?
youcantryreachingme

@youcantryreachingme Ah, điểm tốt ... không đó chỉ là sự trùng hợp trong dữ liệu mẫu.
Paul Holmes

Câu trả lời:


5

Suy nghĩ đầu tiên của tôi là

select
    <best solution>
from
    <all possible combinations>

Phần "giải pháp tốt nhất" được xác định trong câu hỏi - sự khác biệt nhỏ nhất giữa các xe tải được tải nhiều nhất và ít tải nhất. Một chút khác - tất cả các kết hợp - khiến tôi dừng lại để suy nghĩ.

Hãy xem xét một tình huống trong đó chúng tôi có ba đơn đặt hàng A, B và C và ba xe tải. Các khả năng là

Truck 1 Truck 2 Truck 3
------- ------- -------
A       B       C
A       C       B
B       A       C
B       C       A
C       A       B
C       B       A
AB      C       -
AB      -       C
C       AB      -
-       AB      C
C       -       AB
-       C       AB
AC      B       -
AC      -       B
B       AC      -
-       AC      B
B       -       AC
-       B       AC
BC      A       -
BC      -       A
A       BC      -
-       BC      A
A       -       BC
-       A       BC
ABC     -       -
-       ABC     -
-       -       ABC

Table A: all permutations.

Nhiều trong số này là đối xứng. Sáu hàng đầu tiên, ví dụ, chỉ khác nhau ở chỗ mỗi chiếc xe tải được đặt. Vì các xe tải bị nấm nên các sắp xếp này sẽ tạo ra kết quả tương tự. Tôi sẽ bỏ qua điều này cho bây giờ.

Có các truy vấn đã biết để tạo ra hoán vị và kết hợp. Tuy nhiên, những thứ này sẽ tạo ra sự sắp xếp trong một thùng duy nhất. Đối với vấn đề này, tôi cần sắp xếp trên nhiều nhóm.

Nhìn vào đầu ra từ truy vấn "tất cả kết hợp" tiêu chuẩn

;with Numbers as
(
    select n = 1
    union
    select 2
    union
    select 3
)
select
    a.n,
    b.n,
    c.n
from Numbers as a
cross join Numbers as b
cross join Numbers as c
order by 1, 2, 3;


  n   n   n
--- --- ---
  1   1   1
  1   1   2
  1   1   3
  1   2   1
 <snip>
  3   2   3
  3   3   1
  3   3   2
  3   3   3

Table B: cross join of three values.

Tôi đã lưu ý các kết quả hình thành mô hình tương tự như Bảng A. Bằng cách thực hiện bước nhảy vọt xem xét mỗi cột là Đơn hàng 1 , các giá trị để nói xe tải nào sẽ giữ Đơn hàng đó và một hàng là sự sắp xếp Đơn hàng trong các xe tải. Các truy vấn sau đó trở thành

select
    Arrangement             = ROW_NUMBER() over(order by (select null)),
    First_order_goes_in     = a.TruckNumber,
    Second_order_goes_in    = b.TruckNumber,
    Third_order_goes_in     = c.TruckNumber
from Trucks a   -- aka Numbers in Table B
cross join Trucks b
cross join Trucks c

Arrangement First_order_goes_in Second_order_goes_in Third_order_goes_in
----------- ------------------- -------------------- -------------------
          1                   1                    1                   1
          2                   1                    1                   2
          3                   1                    1                   3
          4                   1                    2                   1
  <snip>

Query C: Orders in trucks.

Mở rộng điều này để bao gồm mười bốn Đơn đặt hàng trong dữ liệu mẫu và đơn giản hóa các tên chúng tôi nhận được:

;with Trucks as
(
    select * 
    from (values (1), (2), (3)) as T(TruckNumber)
)
select
    arrangement = ROW_NUMBER() over(order by (select null)),
    First       = a.TruckNumber,
    Second      = b.TruckNumber,
    Third       = c.TruckNumber,
    Fourth      = d.TruckNumber,
    Fifth       = e.TruckNumber,
    Sixth       = f.TruckNumber,
    Seventh     = g.TruckNumber,
    Eigth       = h.TruckNumber,
    Ninth       = i.TruckNumber,
    Tenth       = j.TruckNumber,
    Eleventh    = k.TruckNumber,
    Twelth      = l.TruckNumber,
    Thirteenth  = m.TruckNumber,
    Fourteenth  = n.TruckNumber
into #Arrangements
from Trucks a
cross join Trucks b
cross join Trucks c
cross join Trucks d
cross join Trucks e
cross join Trucks f
cross join Trucks g
cross join Trucks h
cross join Trucks i
cross join Trucks j
cross join Trucks k
cross join Trucks l
cross join Trucks m
cross join Trucks n;

Query D: Orders spread over trucks.

Tôi chọn giữ các kết quả trung gian trong các bảng tạm thời cho thuận tiện.

Các bước tiếp theo sẽ dễ dàng hơn nhiều nếu dữ liệu được UNPIVOTED đầu tiên.

select
    Arrangement,
    TruckNumber,
    ItemNumber  = case NewColumn
                    when 'First'        then 1
                    when 'Second'       then 2
                    when 'Third'        then 3
                    when 'Fourth'       then 4
                    when 'Fifth'        then 5
                    when 'Sixth'        then 6
                    when 'Seventh'      then 7
                    when 'Eigth'        then 8
                    when 'Ninth'        then 9
                    when 'Tenth'        then 10
                    when 'Eleventh'     then 11
                    when 'Twelth'       then 12
                    when 'Thirteenth'   then 13
                    when 'Fourteenth'   then 14
                    else -1
                end
into #FilledTrucks
from #Arrangements
unpivot
(
    TruckNumber
    for NewColumn IN 
    (
        First,
        Second,
        Third,
        Fourth,
        Fifth,
        Sixth,
        Seventh,
        Eigth,
        Ninth,
        Tenth,
        Eleventh,
        Twelth,
        Thirteenth,
        Fourteenth
    )
) as q;

Query E: Filled trucks, unpivoted.

Trọng lượng có thể được giới thiệu bằng cách tham gia vào bảng Đơn hàng.

select
    ft.arrangement,
    ft.TruckNumber,
    TruckWeight = sum(i.Size)
into #TruckWeights
from #FilledTrucks as ft
inner join #Order as i
    on i.OrderId = ft.ItemNumber
group by
    ft.arrangement,
    ft.TruckNumber;

Query F: truck weights

Câu hỏi bây giờ có thể được trả lời bằng cách tìm (các) sự sắp xếp có sự khác biệt nhỏ nhất giữa các xe tải nhiều nhất và tải ít nhất

select
    Arrangement,
    LightestTruck   = MIN(TruckWeight),
    HeaviestTruck   = MAX(TruckWeight),
    Delta           = MAX(TruckWeight) - MIN(TruckWeight)
from #TruckWeights
group by
    arrangement
order by
    4 ASC;

Query G: most balanced arrangements

Thảo luận

Có rất nhiều vấn đề với điều này. Đầu tiên, nó là một thuật toán brute-force. Số lượng hàng trong các bàn làm việc là theo cấp số nhân của số lượng xe tải và đơn đặt hàng. Số lượng hàng trong #Arrangements là (số lượng xe tải) ^ (số lượng đơn đặt hàng). Điều này sẽ không quy mô tốt.

Thứ hai là các truy vấn SQL có số lượng Đơn hàng được nhúng trong chúng. Cách duy nhất để giải quyết vấn đề này là sử dụng SQL động, có vấn đề của riêng nó. Nếu số lượng đơn đặt hàng là hàng ngàn thì có thể đến một lúc khi SQL được tạo trở nên quá dài.

Thứ ba là sự dư thừa trong các sắp xếp. Điều này làm phồng các bảng trung gian tăng thời gian chạy rất nhiều.

Thứ tư, nhiều hàng trong #Arrangements để trống một hoặc nhiều xe tải. Đây có thể không phải là cấu hình tối ưu. Nó sẽ dễ dàng để lọc ra các hàng này khi tạo. Tôi đã chọn không làm như vậy để giữ cho mã đơn giản và tập trung hơn.

Về mặt này, xử lý các trọng số âm, doanh nghiệp của bạn có nên bắt đầu vận chuyển các balo helium đầy không!

Suy nghĩ

Nếu có một cách để nhập trực tiếp #FillsTrucks từ danh sách các xe tải và Đơn đặt hàng, tôi nghĩ rằng những mối quan tâm tồi tệ nhất sẽ có thể kiểm soát được. Đáng buồn thay, sự bất động của tôi vấp phải rào cản đó. Hy vọng của tôi là một số người đóng góp trong tương lai có thể cung cấp những gì đã lảng tránh tôi.




1 Bạn nói rằng tất cả các mặt hàng cho một đơn đặt hàng phải trên cùng một xe tải. Điều này có nghĩa là nguyên tử chuyển nhượng là Order, không phải OrderDetail. Tôi đã tạo ra những thứ này từ dữ liệu thử nghiệm của bạn, do đó:

select
    OrderId,
    Size = sum(OrderDetailSize)
into #Order
from #OrderDetail
group by OrderId;

Tuy nhiên, không có sự khác biệt nào cho dù chúng tôi dán nhãn các mục trong câu hỏi 'Đặt hàng' hoặc 'Đặt hàng', giải pháp vẫn giữ nguyên.


4

Nhìn vào yêu cầu trong thế giới thực của bạn (mà tôi cho rằng đang cố gắng cân bằng khối lượng công việc của bạn qua một bộ cpus) ...

Có một lý do tại sao bạn cần gán trước các quy trình cho các nhóm / cpus cụ thể không? [Cố gắng để hiểu các yêu cầu thực sự của bạn ]

Ví dụ về 'cập nhật thống kê' của bạn, làm thế nào để bạn biết một hoạt động cụ thể sẽ mất bao lâu? Điều gì xảy ra nếu một hoạt động nhất định gặp phải sự chậm trễ không mong muốn (ví dụ: phân chia bảng / chỉ mục nhiều hơn so với kế hoạch / quá mức, txn của người dùng chạy dài chặn hoạt động 'cập nhật thống kê')?


Đối với mục đích cân bằng tải, tôi thường tạo danh sách các tác vụ (ví dụ: danh sách các bảng cần cập nhật số liệu thống kê) và đặt danh sách đã nói trong bảng (tạm thời / đầu).

Cấu trúc của bảng có thể được sửa đổi theo yêu cầu của bạn, ví dụ:

create table tasks
(id        int             -- auto-increment?

,target    varchar(1000)   -- 'schema.table' to have stats updated, or perhaps ...
,command   varchar(1000)   -- actual command to be run, eg, 'update stats schema.table ... <options>'

,priority  int             -- provide means of ordering operations, eg, maybe you know some tasks will run really long so you want to kick them off first
,thread    int             -- identifier for parent process?
,start     datetime        -- default to NULL
,end       datetime        -- default to NULL
)

Tiếp theo, tôi khởi động X số quy trình đồng thời để thực hiện các hoạt động 'cập nhật thống kê' thực tế, với mỗi quy trình thực hiện như sau:

  • đặt khóa độc quyền trên tasksbàn (đảm bảo không có nhiệm vụ nào được chọn bởi nhiều hơn một quy trình; nên khóa tương đối ngắn)
  • tìm hàng 'đầu tiên' trong đó start = NULL('đầu tiên' sẽ được xác định bởi bạn, ví dụ: đặt hàng theo priority?)
  • cập nhật bộ hàng start = getdate(), thread = <process_number>
  • cam kết cập nhật (và phát hành khóa độc quyền)
  • ghi chú idtarget/commandgiá trị
  • thực hiện thao tác mong muốn chống lại target(thay đổi, chạy command) và khi hoàn thành ...
  • cập nhật tasksvớiend = getdate() where id = <id>
  • lặp lại ở trên cho đến khi không còn nhiệm vụ nào để thực hiện

Với thiết kế trên, giờ đây tôi đã có một hoạt động cân bằng động (chủ yếu).

GHI CHÚ:

  • Tôi cố gắng cung cấp một số loại phương pháp ưu tiên để tôi có thể khởi động các nhiệm vụ chạy dài hơn lên phía trước; trong khi một vài quy trình đang làm việc với các tác vụ chạy dài hơn, các quy trình khác có thể lướt qua danh sách các tác vụ chạy ngắn hơn
  • nếu một quá trình chạy vào độ trễ không có kế hoạch (ví dụ: chạy dài, chặn người dùng txn), các quy trình khác có thể 'nhặt chùng' bằng cách tiếp tục kéo hoạt động 'có sẵn tiếp theo' từ tasks
  • thiết kế của tasksbảng sẽ cung cấp các lợi ích khác, ví dụ: lịch sử thời gian chạy bạn có thể lưu trữ để tham khảo trong tương lai, lịch sử thời gian chạy có thể được sử dụng để sửa đổi các ưu tiên, cung cấp trạng thái của các hoạt động hiện tại, v.v.
  • trong khi 'khóa độc quyền' taskscó vẻ hơi quá mức, hãy nhớ rằng chúng ta phải lập kế hoạch cho vấn đề tiềm năng của 2 (hoặc nhiều) quá trình cố gắng để có được một nhiệm vụ mới cùng một lúc , vì vậy chúng ta cần đảm bảo một nhiệm vụ được chỉ định cho một quy trình (và có, bạn có thể nhận được kết quả tương tự với câu lệnh 'update / select' kết hợp - tùy thuộc vào khả năng ngôn ngữ SQL của RDBMS của bạn); Bước để có được một 'nhiệm vụ' mới phải nhanh chóng, nghĩa là 'khóa độc quyền' nên tồn tại trong thời gian ngắn và trong thực tế, các quy trình sẽ diễn ra taskstheo một cách khá ngẫu nhiên nên sẽ ít bị chặn

Cá nhân, tôi thấy tasksquá trình điều khiển bảng này dễ thực hiện và duy trì hơn một chút ... trái ngược với quy trình phức tạp hơn (thường) là cố gắng gán trước ánh xạ tác vụ / quy trình ... ymmv.


Rõ ràng với ví dụ đáng tin cậy của bạn, bạn không thể đưa xe tải của mình quay trở lại phân phối / kho cho đơn hàng tiếp theo, vì vậy bạn cần chỉ định trước đơn đặt hàng của mình cho các xe tải khác nhau (lưu ý rằng UPS / FedEx / vv cũng phải chỉ định dựa trên các tuyến giao hàng để giảm thời gian giao hàng và sử dụng gas).

Tuy nhiên, trong ví dụ trong thế giới thực của bạn ('cập nhật thống kê'), không có lý do nào mà việc phân công nhiệm vụ / quy trình không thể được thực hiện một cách linh hoạt, do đó đảm bảo cơ hội tốt hơn để cân bằng khối lượng công việc (qua cpus và về việc giảm thời gian chạy tổng thể) .

LƯU Ý: Tôi thường xuyên thấy những người (IT) cố gắng phân công trước các nhiệm vụ của họ (như một hình thức cân bằng tải) trước khi thực sự chạy các nhiệm vụ đã nói, và trong mọi trường hợp, anh ta cuối cùng phải điều chỉnh quá trình chuyển nhượng trước để thực hiện xem xét liên tục các vấn đề nhiệm vụ khác nhau (ví dụ: mức độ phân mảnh trong bảng / chỉ mục, hoạt động đồng thời của người dùng, v.v.).


Đầu tiên, nếu chúng ta nghĩ về 'thứ tự' là bảng và 'orderdetail' là một thống kê cụ thể trên bàn, thì lý do không chia tách là để tránh khóa chờ giữa các thùng cạnh tranh. Traceflag 7471 được thiết kế để loại bỏ vấn đề này, nhưng trong thử nghiệm của tôi, tôi vẫn gặp vấn đề về khóa.
Paul Holmes

Ban đầu tôi hy vọng sẽ tạo ra một giải pháp rất nhẹ. Tạo các nhóm dưới dạng các khối SQL đa tầng đơn lẻ, sau đó 'bắn và quên' từng khối bằng cách sử dụng các công việc SQL Agent tự hủy. tức là không có công việc quản lý xếp hàng. Tuy nhiên, sau đó tôi thấy tôi không thể dễ dàng đo được khối lượng công việc theo thống kê - số lượng hàng không cắt giảm. Thực sự không có gì đáng ngạc nhiên, vì số lượng hàng không ánh xạ tuyến tính với lượng IO từ một bảng, hoặc thực sự nghiêm ngặt, sang bảng tiếp theo. Vì vậy, có, đối với ứng dụng này, nó thực sự có thể tự cân bằng với việc thêm một số quản lý hàng đợi hoạt động như bạn đề xuất.
Paul Holmes

Đối với nhận xét đầu tiên của bạn ... vâng, vẫn còn quyết định (rõ ràng) về mức độ chi tiết của các lệnh ... và các vấn đề tương tranh như: một số lệnh có thể được chạy song song và hưởng lợi từ việc đọc đĩa kết hợp của chúng, v.v. Nhưng tôi vẫn tìm thấy (hơi nhẹ) quản lý hàng đợi động hiệu quả hơn một chút so với các nhóm gán trước :-) Bạn đã có một bộ câu trả lời / ý tưởng tốt để làm việc với ... không nên quá khó để đưa ra giải pháp cung cấp một số cân bằng tải tốt.
markp

1

tạo và điền vào bảng số như bạn muốn. Đây chỉ là một lần tạo.

 create table tblnumber(number int not null)

    insert into tblnumber (number)
    select ROW_NUMBER()over(order by a.number) from master..spt_values a
    , master..spt_values b

    CREATE unique clustered index CI_num on tblnumber(number)

Tạo bảng xe tải

CREATE TABLE #PaulWhiteTruck (
Truckid int NOT NULL)

insert into #PaulWhiteTruck
values(113),(203),(303)

declare @PaulTruckCount int
Select @PaulTruckCount= count(*) from #PaulWhiteTruck

CREATE TABLE #OrderDetail (
id int identity(1,1),
OrderId int NOT NULL,
OrderDetailId int NOT NULL PRIMARY KEY,
OrderDetailSize int NOT NULL,
TruckId int NULL
)

INSERT
#OrderDetail (OrderId, OrderDetailId, OrderDetailSize)
VALUES
(
1 ,100 ,75 ),(2 ,101 ,5 ),
(2 ,102 ,5 ),(2 ,103 ,5 ),
(2 ,104 ,5 ),(2 ,105 ,5 ),
(3 ,106 ,100),(4 ,107 ,1 ),
(5 ,108 ,11 ),(6 ,109 ,21 ),
(7 ,110 ,49 ),(8 ,111 ,25 ),
(8 ,112 ,25 ),(9 ,113 ,40 ),
(10 ,114 ,49 ),(11 ,115 ,10 ),
(11 ,116 ,10 ),(12 ,117 ,15 ),
(13 ,118 ,18 ),(14 ,119 ,26 )

Tôi đã tạo một OrderSummaryBảng

create table #orderSummary(id int identity(1,1),OrderId int ,TruckOrderSize int
,bit_value AS
CONVERT
(
integer,
POWER(2, id - 1)
)
PERSISTED UNIQUE CLUSTERED)
insert into #orderSummary
SELECT OrderId, SUM(OrderDetailSize) AS TruckOrderSize
FROM #OrderDetail GROUP BY OrderId

DECLARE @max integer =
POWER(2,
(
SELECT COUNT(*) FROM #orderSummary 
)
) - 1
declare @Delta int
select @Delta= max(TruckOrderSize)-min(TruckOrderSize)   from #orderSummary

Vui lòng kiểm tra giá trị Delta của tôi và cho tôi biết nếu nó sai

;WITH cte 
     AS (SELECT n.number, 
                c.* 
         FROM   dbo.tblnumber AS N 
                CROSS apply (SELECT s.orderid, 
                                    s.truckordersize 
                             FROM   #ordersummary AS s 
                             WHERE  n.number & s.bit_value = s.bit_value) c 
         WHERE  N.number BETWEEN 1 AND @max), 
     cte1 
     AS (SELECT c.number, 
                Sum(truckordersize) SumSize 
         FROM   cte c 
         GROUP  BY c.number 
        --HAVING sum(TruckOrderSize) between(@Delta-25) and (@Delta+25) 
        ) 
SELECT c1.*, 
       c.orderid 
FROM   cte1 c1 
       INNER JOIN cte c 
               ON c1.number = c.number 
ORDER  BY sumsize 

DROP TABLE #orderdetail 

DROP TABLE #ordersummary 

DROP TABLE #paulwhitetruck 

Bạn có thể kiểm tra kết quả của CTE1, nó có thể Permutation and Combination of order along with their size.

Nếu cách tiếp cận của tôi là chính xác cho đến đây, thì tôi cần ai đó giúp đỡ.

Nhiệm vụ đang chờ xử lý:

bộ lọc và phân chia kết quả của CTE13 phần ( Truck count) sao cho Orderidduy nhất trong mỗi nhóm và mỗi phần T ruckOrderSizegần với Delta.


Kiểm tra câu trả lời mới nhất của tôi. Tôi bỏ lỡ một truy vấn trong khi đăng bài, không ai chỉ ra lỗi của tôi. Dán và chạy
KumarHarsh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.