Đếm hàng với PDO


192

Có nhiều tuyên bố mâu thuẫn xung quanh. Cách tốt nhất để đếm hàng bằng cách sử dụng PDO trong PHP là gì? Trước khi sử dụng PDO, tôi chỉ đơn giản là sử dụng mysql_num_rows.

fetchAll là điều tôi không muốn vì đôi khi tôi có thể xử lý các bộ dữ liệu lớn, vì vậy không tốt cho việc sử dụng của tôi.

Bạn có đề nghị nào không?

Câu trả lời:


264
$sql = "SELECT count(*) FROM `table` WHERE foo = ?"; 
$result = $con->prepare($sql); 
$result->execute([$bar]); 
$number_of_rows = $result->fetchColumn(); 

Không phải là cách thanh lịch nhất để làm điều đó, cộng với nó liên quan đến một truy vấn thêm.

PDO có PDOStatement::rowCount(), dường như không hoạt động trong MySql. Thật là một nỗi đau.

Từ tài liệu PDO:

Đối với hầu hết các cơ sở dữ liệu, PDOStatement :: rowCount () không trả về số lượng hàng bị ảnh hưởng bởi câu lệnh SELECT. Thay vào đó, hãy sử dụng PDO :: query () để đưa ra câu lệnh SELECT COUNT (*) có cùng vị từ với câu lệnh SELECT dự định của bạn, sau đó sử dụng PDOStatement :: fetchColumn () để lấy số lượng hàng sẽ được trả về. Ứng dụng của bạn sau đó có thể thực hiện hành động chính xác.

EDIT: Ví dụ mã trên sử dụng một câu lệnh được chuẩn bị, trong nhiều trường hợp có lẽ không cần thiết cho mục đích đếm hàng, vì vậy:

$nRows = $pdo->query('select count(*) from blah')->fetchColumn(); 
echo $nRows;

điều này có nghĩa là thực hiện một truy vấn cơ sở dữ liệu bổ sung. Tôi giả sử anh ta đã thực hiện một truy vấn chọn và bây giờ muốn biết có bao nhiêu hàng được trả về.
nickf

1
nickf là chính xác. Mặc dù vậy, mysql_num_rows () sẽ không hoạt động khi sử dụng PDO?
James

Đúng, rõ ràng là có PDOStatement :: rowCount () nhưng điều đó không hoạt động trong MySql
karim79

11
Sử dụng phương pháp này, fetchColumn()trả về một chuỗi "1234" ... EDIT của bạn có echo count($nRows);- count()là một hàm mảng: P. Tôi cũng khuyên bạn nên nhập kết quả từ fetchColumn()một số nguyên. $count = (int) $stmt->fetchColumn()
Cobby

1
@ karim79 Cách tiếp cận câu lệnh không chuẩn bị chỉ trả về 1 thay vì số hàng thực tế. Các tuyên bố chuẩn bị hoạt động tốt. Điều gì có thể là vấn đề?
SilentAssassin

86

Như tôi đã viết trước đây trong một câu trả lời cho một câu hỏi tương tự , lý do duy nhất có mysql_num_rows()hiệu quả là vì nó đã tìm nạp nội bộ tất cả các hàng để cung cấp cho bạn thông tin đó, ngay cả khi nó không giống với bạn.

Vì vậy, trong PDO, các tùy chọn của bạn là:

  1. Sử dụng FOUND_ROWS()chức năng của MySQL .
  2. Sử dụng fetchAll()chức năng của PDO để tìm nạp tất cả các hàng vào một mảng, sau đó sử dụng count()nó.
  3. Làm một truy vấn thêm SELECT COUNT(*), như karim79 đề xuất.

8
Cảm ơn bạn đã giáo dục tôi thêm về mysql_num_rows () có vẻ như đó có thể là một nút cổ chai quan trọng mà tôi đang tự đưa ra. Cảm ơn một lần nữa.
James

2
fetch_all thực sự là fetch ALL () :)
động

2
Tùy chọn 2 không được khuyến khích nếu kết quả lớn.
Edson Horacio Junior

FOUND_lawS () sẽ bị xóa khỏi MySQL, vì vậy vui lòng kiểm tra liên kết đến FOUND_lawS trước khi sử dụng nếu bạn đã quen với điều này.
anatak

28

Như nó thường xảy ra, câu hỏi này là khó hiểu như địa ngục. Mọi người đang đến đây có hai nhiệm vụ khác nhau trong tâm trí:

  1. Họ cần biết có bao nhiêu hàng trong bảng
  2. Họ cần biết liệu một truy vấn có trả về bất kỳ hàng nào không

Đó là hai nhiệm vụ hoàn toàn khác nhau không có điểm chung và không thể giải quyết bằng cùng một chức năng. Trớ trêu thay, đối với cả haiPDOStatement::rowCount() chức năng thực tế phải được sử dụng.

Hãy xem tại sao

Đếm các hàng trong bảng

Trước khi sử dụng PDO tôi chỉ đơn giản là sử dụng mysql_num_rows().

Có nghĩa là bạn đã làm sai. Sử dụng mysql_num_rows()hoặc rowCount()để đếm số lượng hàng trong bảng là một thảm họa thực sự về mặt tiêu thụ tài nguyên máy chủ. Một cơ sở dữ liệu phải đọc tất cả các hàng từ đĩa, tiêu thụ bộ nhớ trên máy chủ cơ sở dữ liệu, sau đó gửi tất cả đống dữ liệu này đến PHP, cũng tiêu tốn bộ nhớ của quá trình PHP, làm gánh nặng máy chủ của bạn hoàn toàn không có lý do.
Bên cạnh đó, việc chọn các hàng chỉ để đếm chúng đơn giản là vô nghĩa. Một count(*)truy vấn phải được chạy thay thế. Cơ sở dữ liệu sẽ đếm các bản ghi ra khỏi chỉ mục, mà không cần đọc các hàng thực tế và sau đó chỉ có một hàng được trả về.

Đối với mục đích này, mã được đề xuất trong câu trả lời được chấp nhận là công bằng.

Đếm các hàng số trả về.

Trường hợp sử dụng thứ hai không phải là thảm họa như là vô nghĩa: trong trường hợp bạn cần biết liệu truy vấn của bạn có trả lại bất kỳ dữ liệu nào không, bạn luôn có dữ liệu đó!

Nói, nếu bạn chỉ chọn một hàng. Được rồi, bạn có thể sử dụng hàng được tìm nạp làm cờ:

$stmt->execute();
$row = $stmt->fetch();
if (!$row) { // here! as simple as that
    echo 'No data found';
}

Trong trường hợp bạn cần nhận được nhiều hàng, thì bạn có thể sử dụng fetchAll().

fetchAll() là điều tôi sẽ không muốn vì đôi khi tôi có thể xử lý các bộ dữ liệu lớn

Tất nhiên, đối với trường hợp sử dụng đầu tiên, nó sẽ tệ gấp đôi. Nhưng như chúng ta đã biết, chỉ cần không chọn các hàng chỉ để đếm chúng, không có rowCount()cũng không có fetchAll().

Nhưng trong trường hợp bạn thực sự sử dụng các hàng đã chọn, không có gì sai khi sử dụng fetchAll(). Hãy nhớ rằng trong một ứng dụng web, bạn không bao giờ nên chọn một số lượng lớn hàng. Chỉ hàng sẽ được thực sự được sử dụng trên một trang web nên được lựa chọn, vì thế bạn phải sử dụng LIMIT, WHEREhoặc một điều khoản tương tự trong SQL của bạn. Và với lượng dữ liệu vừa phải như vậy, bạn hoàn toàn có quyền sử dụng fetchAll(). Và một lần nữa, chỉ cần sử dụng kết quả của chức năng này trong điều kiện:

$stmt->execute();
$data = $stmt->fetchAll();
if (!$data) { // again, no rowCount() is needed!
    echo 'No data found';
}

Và tất nhiên sẽ là điên rồ tuyệt đối khi chỉ chạy một truy vấn bổ sung để cho biết liệu truy vấn khác của bạn có trả về bất kỳ hàng nào không, như đã đề xuất trong hai câu trả lời hàng đầu.

Đếm số lượng hàng trong một tập kết quả lớn

Trong trường hợp hiếm hoi như vậy khi bạn cần chọn số lượng hàng thực sự lớn (ví dụ trong ứng dụng bảng điều khiển), bạn phải sử dụng truy vấn không có bộ đệm, để giảm lượng bộ nhớ được sử dụng. Nhưng đây là trường hợp thực tế khi rowCount() không có sẵn , do đó cũng không sử dụng chức năng này.

Do đó, đó là trường hợp sử dụng duy nhất khi bạn có thể cần chạy một truy vấn bổ sung, trong trường hợp bạn cần biết ước tính chặt chẽ cho số lượng hàng được chọn.


sẽ hữu ích nếu API cần in ra tổng kết quả của truy vấn tìm kiếm. nó sẽ chỉ cung cấp cho bạn 10 hoặc 15 hàng trở lại, nhưng nó cũng sẽ cho bạn biết rằng có tổng số 284 kết quả.
Andres SK

4
@andufo Không phải vậy. Xin nhớ: một nhà phát triển không bao giờ nên làm theo cách này. Truy vấn tìm kiếm không bao giờ nên trả lại tất cả 284 hàng. 15 phải được trả lại để hiển thị và một hàng từ truy vấn riêng biệt để nói rằng đã tìm thấy 284.
Ý thức chung của bạn

2
Đây là một điểm rất tốt - lúc đầu không trực quan, nhưng hợp lệ. Hầu hết mọi người quên rằng hai truy vấn SQL đơn giản là cách nhanh hơn một chút lớn hơn. Để biện minh cho bất kỳ việc đếm nào, bạn phải có một truy vấn rất chậm không thể tối ưu hóa và sẽ mang lại ít kết quả. Cảm ơn đã chỉ ra rằng!
PeerBr

@ Ý thức chung của bạn: Chỉ cần chắc chắn: sẽ không fetch ALL () là một ý tưởng tồi nếu tập kết quả rất lớn? Sẽ không tốt hơn khi sử dụng fetch () để lấy dữ liệu liên tiếp.
timmornYE

@timmornYE đó chính xác là những gì được nói trong đoạn cuối câu trả lời của tôi
Ý thức chung của bạn

21

Đây là siêu muộn, nhưng tôi gặp phải vấn đề và tôi làm điều này:

function countAll($table){
   $dbh = dbConnect();
   $sql = "select * from `$table`";

   $stmt = $dbh->prepare($sql);
    try { $stmt->execute();}
    catch(PDOException $e){echo $e->getMessage();}

return $stmt->rowCount();

Nó thực sự đơn giản và dễ dàng. :)


19
Chọn tất cả các dữ liệu chỉ để đếm nó, là trái với các quy tắc cơ bản nhất của tương tác cơ sở dữ liệu.
Ý thức chung của bạn

Có thể bạn muốn có một thanh tiến trình cho tất cả các giá trị được trả về, vì vậy bạn cần biết số lượng hàng trả trước.
Jonny

18

Tôi đã kết thúc bằng cách sử dụng này:

$result = $db->query($query)->fetchAll();

if (count($result) > 0) {
    foreach ($result as $row) {
        echo $row['blah'] . '<br />';
    }
} else {
    echo "<p>Nothing matched your query.</p>";
}

12

Bài đăng này đã cũ nhưng việc đếm hàng trong php với PDO rất đơn giản

$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();

3
Xem tài liệu trích dẫn trong câu trả lời của karim79. Điều này đôi khi hoạt động nhưng không đáng tin cậy.
octern

5

Đây là một bài viết cũ, nhưng nhận được thất vọng tìm kiếm các lựa chọn thay thế. Thật đáng tiếc khi PDO thiếu tính năng này, đặc biệt là khi PHP và MySQL có xu hướng song hành với nhau.

Có một lỗ hổng đáng tiếc trong việc sử dụng fetchColumn () vì bạn không còn có thể sử dụng tập kết quả đó (một cách hiệu quả) khi fetchColumn () di chuyển kim sang hàng tiếp theo. Vì vậy, ví dụ, nếu bạn có một kết quả tương tự như

  1. Trái cây-> Chuối
  2. Trái cây-> Táo
  3. Trái cây-> Cam

Nếu bạn sử dụng fetchColumn (), bạn có thể phát hiện ra rằng có 3 quả được trả về, nhưng nếu bây giờ bạn lặp lại kết quả, bạn chỉ có hai cột, Giá của fetchColumn () là mất cột kết quả đầu tiên chỉ để tìm Có bao nhiêu hàng được trả lại. Điều đó dẫn đến mã hóa cẩu thả và kết quả lỗi hoàn toàn nếu được thực hiện.

Vì vậy, bây giờ, bằng cách sử dụng fetchColumn (), bạn phải thực hiện và truy vấn MySQL và truy vấn hoàn toàn mới chỉ để có được một tập kết quả làm việc mới. (hy vọng không thay đổi kể từ truy vấn cuối cùng của bạn), tôi biết, không chắc, nhưng nó có thể xảy ra. Ngoài ra, chi phí chung của các truy vấn kép trên tất cả xác thực số hàng. Mà ví dụ này là nhỏ, nhưng phân tích 2 triệu hàng trên một truy vấn đã tham gia, không phải là một mức giá dễ chịu để trả.

Tôi yêu PHP và hỗ trợ tất cả mọi người tham gia vào sự phát triển của nó cũng như cộng đồng sử dụng PHP hàng ngày, nhưng thực sự hy vọng điều này sẽ được giải quyết trong các bản phát hành trong tương lai. Đây thực sự là "khiếu nại duy nhất của tôi với PHP PDO, nếu không thì đây là một lớp tuyệt vời.


2

Trả lời điều này bởi vì tôi đã tự nhốt mình với nó bằng cách biết điều này và có thể nó sẽ hữu ích.

Hãy nhớ rằng bạn không thể lấy kết quả hai lần. Bạn phải lưu kết quả tìm nạp vào mảng, đếm số hàng count($array)và kết quả đầu ra với foreach. Ví dụ:

$query = "your_query_here";
$STH = $DBH->prepare($query);
$STH->execute();
$rows = $STH->fetchAll();
//all your results is in $rows array
$STH->setFetchMode(PDO::FETCH_ASSOC);           
if (count($rows) > 0) {             
    foreach ($rows as $row) {
        //output your rows
    }                       
}

1

Nếu bạn chỉ muốn lấy số lượng hàng (không phải dữ liệu) tức là. sử dụng COUNT (*) trong câu lệnh đã chuẩn bị, tất cả những gì bạn cần làm là lấy kết quả và đọc giá trị:

$sql = "SELECT count(*) FROM `table` WHERE foo = bar";
$statement = $con->prepare($sql); 
$statement->execute(); 
$count = $statement->fetch(PDO::FETCH_NUM); // Return array indexed by column number
return reset($count); // Resets array cursor and returns first value (the count)

Trên thực tế, việc lấy tất cả các hàng (dữ liệu) để thực hiện một số đếm đơn giản là một sự lãng phí tài nguyên. Nếu tập kết quả lớn, máy chủ của bạn có thể bị sặc.



1

Để sử dụng các biến trong một truy vấn bạn phải sử dụng bindValue()hoặc bindParam(). Và không nối các biến với" . $variable . "

$statement = "SELECT count(account_id) FROM account
                  WHERE email = ? AND is_email_confirmed;";
$preparedStatement = $this->postgreSqlHandler->prepare($statement);
$preparedStatement->bindValue(1, $account->getEmail());
$preparedStatement->execute();
$numberRows= $preparedStatement->fetchColumn();

GL


0

Khi vấn đề của mysql là cách tính hoặc lấy bao nhiêu hàng trong một bảng với PDO PHP, tôi sử dụng cái này

// count total number of rows
$query = "SELECT COUNT(*) as total_rows FROM sometable";
$stmt = $con->prepare($query);

// execute query
$stmt->execute();

// get total rows
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$total_rows = $row['total_rows'];

các khoản tín dụng được gửi tới Mike @ codeofaninja.com


-1

Một lót nhanh chóng để có được mục đầu tiên trở lại. Điều này là tốt cho các truy vấn rất cơ bản.

<?php
$count = current($db->query("select count(*) from table")->fetch());
?>

Tài liệu tham khảo


-1

Tôi đã thử $count = $stmt->rowCount();với Oracle 11.2 và nó không hoạt động. Tôi quyết định sử dụng một vòng lặp for như hiển thị dưới đây.

   $count =  "";
    $stmt =  $conn->prepare($sql);
    $stmt->execute();
   echo "<table border='1'>\n";
   while($row = $stmt->fetch(PDO::FETCH_OBJ)) {
        $count++;
        echo "<tr>\n";
    foreach ($row as $item) {
    echo "<td class='td2'>".($item !== null ? htmlentities($item, ENT_QUOTES):"&nbsp;")."</td>\n";
        } //foreach ends
        }// while ends
        echo "</table>\n";
       //echo " no of rows : ". oci_num_rows($stmt);
       //equivalent in pdo::prepare statement
       echo "no.of rows :".$count;

-1

Đối với các truy vấn thẳng nơi tôi muốn một hàng cụ thể và muốn biết nếu nó được tìm thấy, tôi sử dụng một cái gì đó như:

function fetchSpecificRow(&$myRecord) {
    $myRecord = array();
    $myQuery = "some sql...";
    $stmt = $this->prepare($myQuery);
    $stmt->execute(array($parm1, $parm2, ...));
    if ($myRecord = $stmt->fetch(PDO::FETCH_ASSOC)) return 0;
    return $myErrNum;
}

-1

Có một giải pháp đơn giản. Nếu bạn sử dụng PDO, hãy kết nối với DB của bạn như thế này:

try {
    $handler = new PDO('mysql:host=localhost;dbname=name_of_your_db', 'your_login', 'your_password'); 
    $handler -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} 
    catch (PDOException $e) {   
    echo $e->getMessage();
}

Sau đó, truy vấn tới DB sẽ là:

$query = $handler->query("SELECT id FROM your_table WHERE ...");

Và cuối cùng, để đếm các hàng khớp với truy vấn của bạn, hãy viết như thế này

$amountOfRows = $query->rowcount();

Tại sao điều này trả lại cho tôi -1 trong hầu hết thời gian? Ngay cả khi có dữ liệu. .
Ingus

-1

fetchColumn ()

được sử dụng nếu muốn lấy số lượng bản ghi [effisien]

$sql   = "SELECT COUNT(*) FROM fruit WHERE calories > 100";
$res   = $conn->query($sql);
$count = $res->fetchColumn(); // ex = 2

truy vấn()

được sử dụng nếu muốn lấy dữ liệu và số lượng bản ghi [tùy chọn]

$sql = "SELECT * FROM fruit WHERE calories > 100";
$res = $conn->query($sql);

if ( $res->rowCount() > 0) {

    foreach ( $res as $row ) {
        print "Name: {$row['NAME']} <br />";
    }

}
else {
    print "No rows matched the query.";
}

PDOStatement :: rowCount


Bạn không bao giờ nên làm điều đó. Trên một bàn lớn hơn hoặc ít hơn, nó sẽ ăn hết bộ nhớ máy chủ và làm sập máy chủ
Ý thức chung của bạn vào

Bây giờ câu trả lời của bạn chỉ cần sao chép một tá câu trả lời hiện có.
Ý thức chung của bạn

-2

khi bạn tạo một COUNT (*) trong câu lệnh mysql của bạn như trong

$q = $db->query("SELECT COUNT(*) FROM ...");

truy vấn mysql của bạn đã đếm số lượng kết quả tại sao lại đếm trong php? để có được kết quả của mysql của bạn

$q = $db->query("SELECT COUNT(*) as counted FROM ...");
$nb = $q->fetch(PDO::FETCH_OBJ);
$nb = $nb->counted;

$nbsẽ chứa số nguyên bạn đã đếm với câu lệnh mysql của bạn hơi lâu để viết nhưng thực thi nhanh

Chỉnh sửa: xin lỗi vì bài đăng sai nhưng như một số ví dụ hiển thị truy vấn có số đếm, tôi đã đề xuất sử dụng kết quả mysql, nhưng nếu bạn không sử dụng số đếm trong sql fetchAll () thì hiệu quả, nếu bạn lưu kết quả vào một biến bạn sẽ không mất một dòng.

$data = $dbh->query("SELECT * FROM ...");
$table = $data->fetchAll(PDO::FETCH_OBJ);

count($table)sẽ trả về số lượng hàng và bạn vẫn có thể sử dụng kết quả sau khi thích $row = $table[0] hoặc sử dụngforeach

foreach($table as $row){
  print $row->id;
}

1
Câu hỏi đặt ra là làm thế nào để có được số đếm khi bạn KHÔNG tạo ra một COUNT (*) trong câu lệnh mysql
của bạn

-2

Đây là một phần mở rộng được tạo tùy chỉnh của lớp PDO, với chức năng trợ giúp để lấy số lượng hàng được bao gồm bởi tiêu chí "WHERE" của truy vấn cuối cùng.

Tuy nhiên, bạn có thể cần thêm nhiều 'trình xử lý', tùy thuộc vào lệnh bạn sử dụng. Ngay bây giờ, nó chỉ hoạt động cho các truy vấn sử dụng "TỪ" hoặc "CẬP NHẬT".

class PDO_V extends PDO
{
    private $lastQuery = null;

    public function query($query)
    {
        $this->lastQuery = $query;    
        return parent::query($query);
    }
    public function getLastQueryRowCount()
    {
        $lastQuery = $this->lastQuery;
        $commandBeforeTableName = null;
        if (strpos($lastQuery, 'FROM') !== false)
            $commandBeforeTableName = 'FROM';
        if (strpos($lastQuery, 'UPDATE') !== false)
            $commandBeforeTableName = 'UPDATE';

        $after = substr($lastQuery, strpos($lastQuery, $commandBeforeTableName) + (strlen($commandBeforeTableName) + 1));
        $table = substr($after, 0, strpos($after, ' '));

        $wherePart = substr($lastQuery, strpos($lastQuery, 'WHERE'));

        $result = parent::query("SELECT COUNT(*) FROM $table " . $wherePart);
        if ($result == null)
            return 0;
        return $result->fetchColumn();
    }
}

Vấn đề không đáng nỗ lực. Một số như vậy là cần thiết để hiếm khi người ta không cần một phần mở rộng dành riêng. Chưa kể nó không hỗ trợ các tuyên bố đã chuẩn bị - lý do duy nhất để sử dụng PDO.
Ý thức chung của bạn

-2

Bạn có thể kết hợp phương thức tốt nhất vào một dòng hoặc hàm và có tự động tạo truy vấn mới cho bạn:

function getRowCount($q){ 
    global $db;
    return $db->query(preg_replace('/SELECT [A-Za-z,]+ FROM /i','SELECT count(*) FROM ',$q))->fetchColumn();
}

$numRows = getRowCount($query);

Không có gì tốt nhất trong phương pháp này. Để chạy một truy vấn bổ sung chỉ để biết có bao nhiêu hàng được trả về hoàn toàn không có ý nghĩa.
Ý thức chung của bạn

-2
<table>
      <thead>
           <tr>
                <th>Sn.</th>
                <th>Name</th>
           </tr>
      </thead>
      <tbody>
<?php
     $i=0;
     $statement = $db->prepare("SELECT * FROM tbl_user ORDER BY name ASC");
     $statement->execute();
     $result = $statement->fetchColumn();
     foreach($result as $row) {
        $i++;
    ?>  
      <tr>
         <td><?php echo $i; ?></td>
         <td><?php echo $row['name']; ?></td>
      </tr>
     <?php
          }
     ?>
     </tbody>
</table>

1
Đây là một ý tưởng tồi thực sự. Chọn tất cả các hàng, đó là một sự lãng phí rất lớn về băng thông.
Nafees

-2
function count_x($connect) {  
 $query = "  SELECT * FROM tbl WHERE id = '0' ";  
 $statement = $connect->prepare($query);  $statement->execute();  
 $total_rows = $statement->rowCount();  
 return $total_rows; 
}

-4

Sử dụng tham số array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL), hiển thị -1:

Usen parametro array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL), bán ello sin -1

thí dụ:

$res1 = $mdb2->prepare("SELECT clave FROM $tb WHERE id_usuario='$username' AND activo=1 and id_tipo_usuario='4'", array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL));
$res1->execute();

$count=$res1->rowCount();
echo $count;
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.