Pengaturan REPMGR Menggunakan WITNESS di PostgreSQL 10

Setting Up REPMGR With WITNESS For PostgreSQL 10 Database

This blog post will go over how to set up and implement repmgr which is the PostgreSQL application to manage replication between primary and replica nodes, allowing for quick and easy failover and rebuilding of replicas. For reference, all commands are run as root. For the commands that need to be run as the Postgres user, I will run them using su. As an example:

su - postgres -c 'COMMAND RUN AS POSTGRES USER'

Outline

  • The Setup
  • SSH
  • REPMGR
  • PostgreSQL Primary Configuration
  • PostgreSQL Replica Configuration
  • PostgreSQL Witness Configuration
  • Summary

The Setup

The environment that I will be working in consists of four CentOS 7 servers with a default install of PostgreSQL 10 installed using yum from the PostgreSQL 10 repo.  With PostgreSQL installed on each server, the Postgres user will already be on each server. I will configure the first server as the primary master. The second and third server will become replicas of the primary master. Then the fourth and final server will become a witness server used for voting in automatic failover scenarios.

base-centos-1 = primary master
base-centos-2 = replica
base-centos-3 = replica
base-centos-4 = witness

SSH

In order for repmgr to work correctly, the Postgres user account will need to be able to SSH to each of the other servers with no prompt.  In order to do this I went to each server and generated an SSH key, and then collected all of the public SSH keys and placed them in an authorzied_keys file on each server.  This allowed me to SSH between the servers with no prompt. I have SELINUX disabled by default. If you are using SELINUX, make sure it is set up correctly for SSH and authorized keys.

On each server, I did the following:

1) [ON ALL SERVERS] Become the Postgres user, create the ssh key, then cat out the public key:

su - postgres -c 'ssh-keygen'
su - postgres -c 'cat ~/.ssh/id_rsa.pub'

2) [ON ALL SERVERS] Create an authorized keys file and set the permissions:

su - postgres -c 'touch ~/.ssh/authorized_keys'
su - postgres -c 'chmod 600 ~/.ssh/authorized_keys'
su - postgres -c 'vi ~/.ssh/authorized_keys'

3) [ON ALL SERVERS] Place all of the public keys from Step 1 into the authorized_keys files created in Step 2.  The authorized_keys file contents looked like the following on all of my servers, with each key as a new line:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXxjFY8dLs2GVRpDY7asAK5SvwITPVSJN9ItnwsVtzCpZgX/Mbnkc/jHgwuIGb0srh/KthByyYJi14QViI+x7xVQm8eyuqMBtORt9rl6rLF73H/gYPO9jONIbD/yihxJMWmJK1Ro6Armhfj5OyTXyW6vjbXqvl7fuSi4n13ubW+G7Pnk8jDK+5rOFYve6Czmde7cQPeueo7sY4oZCbhO+Vr+HwK6qUNUsP3/iRj/bjpbNh8gaqQDl5y5XCNV32y3IhgMgsimO4EkSbb03y0lcWI8dJ6asnXUZumvUwasrFcQ2SFRqb/F3kK4L2Ofy9qYo0yV+FyCuJlsoEur1IRKF postgres@base-centos-1
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYHmDFOtYc/VDxccNRQEnDYBTE8QDiUTMX46PX1p5tvs6qvP3VPMEccs4um0YVFXZTmbnvyeN3bBPe23NS5Pal6ySfAxIdAAOt5YzKoJ0BJAoHksfTchXsCvSs1zIBLXLSRTYdmjb2s+EWxB9elle4Z2KbZrXbzVkSQuUOdMtbjTPUfIqcVh8kokCznpVjKSEXtHg9Vx1Whg20brw99EzhcBeS5q+jJtQbaSa6VG3VUmsznxtoWiA9EgMZ9C7hcmxTrtKNEJrQvd1LpBObTYbueV7+MVlgShTct88alun12iheT6x6ien445X1lYjLmkjGT8CHNqdos+VQR9sgT5R7 postgres@base-centos-2
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCgQNra4/rDOVWr5uV5nSa49yPLlgAJ6crsYKpvhfRr3L5J/T48QE468Am5t3lA318Nst2FbObq7dduqBNhOQDurPlTiPd9cWQsj0mVySnZ2gD7sc8epyRcNbR5cNBum2JKkez4X8FxArMZwYkvPd28dAPFd30NwLYgIFhQ/jzScweyfX1RYp4s+AOI0XpBAiuQZ6r5Gxz+h61jmsbCwM4ZmT4J8OFIF/x/GFExF126PB2PqEg41OK2AGZ2eneTtaoDxMoVDPuy1vhdUw/aODf1dPZYRTn2rwbdXDW7E6Y8/dxT3Lo1n8v/syboy/MsF50NdYzwOb0/Q4dtYsVPpemV postgres@base-centos-3
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5UAsmEkw9INHwXL6cpHqOy5O8VIpvLsfklzoHYfmGxBhGqi6nZzV/+TAzpotrmAf7PIUEdzWOm1lTfii1iRU821ks1bSPN2FeBXzK5Mhtl6KHpIXeDOS64WEJRcen2cQIw9f5f6Ji6PBlc3AQBvspxuNtth6Y0vOTq2N+kFTGOM95kuuIzRs7QvU0XrCoMKOn/BBE+e8ad72gwjCP1q9aNheDJBCsB9WhJerN+LOLr2gJiA0Ha6xxSGQ5WoAB17nYSQunsSvvAU079wfVt/e6OMozyNC6X/+I7IRcRg8IHS+aMKvKrasfi5PTawqoideB4QngM6lHmUR9/MFf9Ikt postgres@base-centos-4

4) I am now able to become the Postgres user and SSH to any of the other servers and log in without a password prompt. This is an example on base-centos-1:

[root@base-centos-1 vagrant]# su - postgres
Last login: Mon Jul 22 18:27:34 UTC 2019 on pts/0
-bash-4.2$ ssh base-centos-2
Last login: Mon Jul 22 18:22:31 2019
-bash-4.2$ hostname
base-centos-2

REPMGR

For repmgr, I first installed the repmgr software, and then configured it on each server to make sure all of the configurations are correct. With the installation of PostgreSQL 10 on my servers, I installed the PostgreSQL repository using the following RPM. This repo also includes repmgr, which is where I am installing the software from. You can find the different yum repositories from https://yum.postgresql.org/repopackages.php:

yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

1) [ON ALL SERVERS] I installed repmgr from the PostgreSQL 10 repository. Make sure you install the correct repmgr for the version of PosgreSQL that you are running:

yum install -y repmgr10.x86_64

2) [ON ALL SERVERS] Next, I configured repmgr on each server with the servers specific details. Make sure to update each according to the server and paths. As an example for each server, I updated the node_id, node_name, and conninfo to match the servers I was configuring. If you are using a different version of PostgreSQL you will want to make sure you update the paths for the version you are using. Do NOT assume the paths in my example will be the same on your system. In my setup, the configuration file was located at /etc/repmgr/10/repmgr.conf.

node_id=101
node_name='base-centos-1'
conninfo='host=base-centos-1 dbname=repmgr user=repmgr'
data_directory='/var/lib/pgsql/10/data/'
config_directory='/var/lib/pgsql/10/data'
log_file='/var/log/repmgr.log'
repmgrd_service_start_command = '/usr/pgsql-10/bin/repmgrd -d'
repmgrd_service_stop_command = 'kill `cat $(/usr/pgsql-10/bin/repmgrd --show-pid-file)`'
promote_command='repmgr standby promote -f /etc/repmgr/10/repmgr.conf --siblings-follow --log-to-file'
follow_command='repmgr standby follow -f /etc/repmgr/10/repmgr.conf --log-to-file'
failover=automatic
reconnect_attempts=3
reconnect_interval=5
ssh_options='-q -o StrictHostKeyChecking=no -o ConnectTimeout=10'

3) [ON ALL SERVERS] I created the log file that I configured in Step 2 so I would not get an error when starting the service:

su - postgres -c 'touch /var/log/repmgr.log'

 

PostgreSQL Primary Configuration

Next, I started working on the primary server to get it set up within repmgr. For the configuration of the pg_bha.conf and postgresql.conf, I am only updating them on the primary master, because when I rebuild the replicas from the master, these changes will get copied over. If your server keeps the configuration files separate from the data directory, you will need to update these configurations on all of the servers, not just on the primary master.

1) Create the repmgr user account and repmgr database that will be used for repmgr to manage the cluster. The repmgr user account will be used for replication to the PostgreSQL replica servers to the primary master.

su - postgres -c 'createuser --replication --createdb --createrole --superuser repmgr'
su - postgres -c "psql -c 'ALTER USER repmgr SET search_path TO repmgr, \"\$user\", public;'"
su - postgres -c 'createdb repmgr --owner=repmgr'

2) Update pg_hba.conf to allow the repmgr account to authenticate. With trust being used, this allows the repmgr user account in the database to authenticate without a password. If you are building a production environment, you will want a more secure method using md5 and passwords. These changes won’t take place until the PostgreSQL service is restarted, which I will do after the next step. In my setup this file is located at /var/lib/pgsql/10/data/pg_hba.conf. I found that I had the best results by specifying the IPs.

host replication repmgr 192.168.56.101/32 trust
host replication repmgr 192.168.56.102/32 trust
host replication repmgr 192.168.56.103/32 trust
host replication repmgr 192.168.56.104/32 trust
host repmgr repmgr 192.168.56.101/32 trust
host repmgr repmgr 192.168.56.102/32 trust
host repmgr repmgr 192.168.56.103/32 trust
host repmgr repmgr 192.168.56.104/32 trust

3) Next, I configured the PostgreSQL configuration file to allow for replication to occur by setting the wal_level and other settings. I also add the repmgr shared libraries into the postgresql.conf file. In my setup, the PostgreSQL configuration file is at /var/lib/pgsql/10/data/postgresql.conf.

listen_addresses = '*'
shared_preload_libraries = 'repmgr'
wal_level = replica
archive_mode = on
max_wal_senders = 10
hot_standby = on
archive_command = 'cp -i %p /var/lib/pgsql/10/data/archive/%f'

4) I then created the archive directory that I specified in the PostgresSQL configuration file using the Postgres user to make sure it had the correct permissions, and then I restarted PostgreSQL server to pick up the new settings.

su - postgres -c 'mkdir /var/lib/pgsql/10/data/archive'
systemctl enable postgresql-10.service
systemctl restart postgresql-10.service
systemctl status postgresql-10.service

5) Now that repmgr and PostgreSQL are both configured, I will register my PostgreSQL server with repmgr and then start the repmgr daemon service so that it monitors the status of the replication cluster.

su - postgres -c 'repmgr primary register'
su - postgres -c 'repmgr daemon start'
su - postgres -c 'repmgr daemon status'
ID | Name | Role | Status | Upstream | repmgrd | PID | Paused? | Upstream last seen
----+---------------+---------+-----------+---------------+---------+------+---------+--------------------
101 | base-centos-1 | primary | * running | | running | 5496 | no | n/a

 

PostgreSQL Replica Configuration

At this stage I already have SSH and regmgr installed and repmgr configured on all of the servers. Now I will utilize repmgr to backup the databases off of the primary server and restore them onto the two replica servers base-centos-2 and base-centos-3. Because my pg_hba.conf and postgresql.conf files are in the same directory as my data, these files will also get copied over during the backup and restore process. I will be running the following commands on both of the replica servers, but not the final witness server which I will configure later.

1) Stop PostgreSQL and clear the data directory:

systemctl stop postgresql-10.service
rm -rf /var/lib/pgsql/10/data/*

2) Next, I backed up and restored the data from the primary server, and then started the PostgreSQL server and viewed the status to make sure it was running:

su - postgres -c "repmgr -h base-centos-1 -U repmgr -d repmgr standby clone"
systemctl start postgresql-10.service
systemctl status postgresql-10.service

3) Then I register the replica with the repmgr cluster and start the repmgr daemon service to monitor the server in the cluster:

su - postgres -c 'repmgr standby register -h base-centos-1 -U repmgr'
su - postgres -c 'repmgr daemon start'
su - postgres -c 'repmgr daemon status'
ID | Name | Role | Status | Upstream | repmgrd | PID | Paused? | Upstream last seen
----+---------------+---------+-----------+---------------+---------+------+---------+--------------------
101 | base-centos-1 | primary | * running | | running | 5496 | no | n/a
102 | base-centos-2 | standby | running | base-centos-1 | running | 3414 | no | 1 second(s) ago
103 | base-centos-3 | standby | running | base-centos-1 | running | 4549 | no | 1 second(s) ago

 

4) To verify that the replication is up and running I can run the following commands to validate the replication status:

ON PRIMARY

su - postgres -c 'psql -c "select pid, usename, client_addr, backend_start, state, sync_state from pg_stat_replication;"'
pid | usename | client_addr | backend_start | state | sync_state
-------+---------+----------------+-------------------------------+-----------+------------
15347 | repmgr | 192.168.56.102 | 2019-07-22 18:19:31.232492+00 | streaming | async
15363 | repmgr | 192.168.56.103 | 2019-07-22 18:19:36.566369+00 | streaming | async

ON REPLICAs

su - postgres -c 'psql --pset expanded=auto -c "select * from pg_stat_wal_receiver;"'
-[ RECORD 1 ]---------+------------------------------------------------------------------
pid | 5408
status | streaming
receive_start_lsn | 0/B000000
receive_start_tli | 3
received_lsn | 0/B0025B8
received_tli | 3
last_msg_send_time | 2019-07-22 18:22:17.942712+00
last_msg_receipt_time | 2019-07-22 18:22:17.943306+00
latest_end_lsn | 0/B0025B8
latest_end_time | 2019-07-22 18:19:47.617303+00
slot_name |
conninfo | user=repmgr host='base-centos-1' application_name='base-centos-3'

PostgreSQL Witness Configuration

Having a separate witness is good in the event that there is a network outage and you want a server to vote on who should become the new master. This will help prevent split-brain scenarios, with the understanding that you cannot truly prevent split brains unless you implement a configuration management solution like consul or zookeeper. The important thing to note when setting up a witness is that the witness has to use its own repmgr database. You CANNOT make a standby replica server a witness. I will NOT be restoring the database from the primary onto the witness server, so I will have to repeat a couple of the steps to create the repmgr user and database which will be separate. I then have to configure the pg_hba settings to allow for the repmgr user from the other servers to talk to the witness.

1) Create the repmgr user account and repmgr database that will be used for repmgr to manage the cluster. The repmgr user account will be used for replication to the PostgreSQL replica servers to the primary master.

su - postgres -c 'createuser --replication --createdb --createrole --superuser repmgr'
su - postgres -c "psql -c 'ALTER USER repmgr SET search_path TO repmgr, \"\$user\", public;'"
su - postgres -c 'createdb repmgr --owner=repmgr'

2) Update pg_hba.conf to allow the repmgr account to authenticate. With trust being used, the PosgreSQL user can authenticate without a password. If you are building a production environment, you will want a more secure method using md5 and passwords. These changes won’t take place until PostgreSQL service has been restarted, which I will do after the next step. In my setup, this file is located at /var/lib/pgsql/10/data/pg_hba.conf.

host replication repmgr 192.168.56.101/32 trust
host replication repmgr 192.168.56.102/32 trust
host replication repmgr 192.168.56.103/32 trust
host replication repmgr 192.168.56.104/32 trust
host repmgr repmgr 192.168.56.101/32 trust
host repmgr repmgr 192.168.56.102/32 trust
host repmgr repmgr 192.168.56.103/32 trust
host repmgr repmgr 192.168.56.104/32 trust

3) Register the witness with the primary server in the cluster:

su - postgres -c 'repmgr witness register -h base-centos-1 -U repmgr'
su - postgres -c 'repmgr daemon start'
su - postgres -c 'repmgr daemon status'
ID | Name | Role | Status | Upstream | repmgrd | PID | Paused? | Upstream last seen
----+---------------+---------+-----------+---------------+---------+------+---------+--------------------
101 | base-centos-1 | primary | * running | | running | 5496 | no | n/a
102 | base-centos-2 | standby | running | base-centos-1 | running | 3414 | no | 1 second(s) ago
103 | base-centos-3 | standby | running | base-centos-1 | running | 4549 | no | 1 second(s) ago
104 | base-centos-4 | witness | * running | base-centos-1 | running | 3226 | no | 0 second(s) ago

Summary

Now I have a fully configured repmgr cluster that allows for me to failover from the primary to one of the replicas. As an example, I can run the following to migrate the primary role to one of the standbys. In order to run this, the failover command has to be run on the standby that is becoming the new primary:

su - postgres -c 'repmgr --dry-run -h base-centos-2 standby switchover --siblings-follow'
su - postgres -c 'repmgr -h base-centos-2 standby switchover --siblings-follow'
su - postgres -c 'repmgr cluster show'
su - postgres -c 'repmgr cluster event'

FAILOVER TO base-centos-2

su - postgres -c 'repmgr -h base-centos-2 standby switchover --siblings-follow'
WARNING: following problems with command line parameters detected:
database connection parameters not required when executing UNKNOWN ACTION
NOTICE: executing switchover on node "base-centos-2" (ID: 102)
NOTICE: local node "base-centos-2" (ID: 102) will be promoted to primary; current primary "base-centos-1" (ID: 101) will be demoted to standby
NOTICE: stopping current primary node "base-centos-1" (ID: 101)
NOTICE: issuing CHECKPOINT
DETAIL: executing server command "/usr/pgsql-10/bin/pg_ctl -D '/var/lib/pgsql/10/data' -W -m fast stop"
INFO: checking for primary shutdown; 1 of 60 attempts ("shutdown_check_timeout")
INFO: checking for primary shutdown; 2 of 60 attempts ("shutdown_check_timeout")
NOTICE: current primary has been cleanly shut down at location 0/C000028
NOTICE: promoting standby to primary
DETAIL: promoting server "base-centos-2" (ID: 102) using "/usr/pgsql-10/bin/pg_ctl -w -D '/var/lib/pgsql/10/data' promote"
waiting for server to promote.... done
server promoted
NOTICE: waiting up to 60 seconds (parameter "promote_check_timeout") for promotion to complete
NOTICE: STANDBY PROMOTE successful
DETAIL: server "base-centos-2" (ID: 102) was successfully promoted to primary
INFO: local node 101 can attach to rejoin target node 102
DETAIL: local node's recovery point: 0/C000028; rejoin target node's fork point: 0/C000098
NOTICE: setting node 101's upstream to node 102
WARNING: unable to ping "host=base-centos-1 dbname=repmgr user=repmgr"
DETAIL: PQping() returned "PQPING_NO_RESPONSE"
NOTICE: starting server using "/usr/pgsql-10/bin/pg_ctl -w -D '/var/lib/pgsql/10/data' start"
NOTICE: NODE REJOIN successful
DETAIL: node 101 is now attached to node 102
NOTICE: executing STANDBY FOLLOW on 2 of 2 siblings
INFO: node 104 received notification to follow node 102
INFO: STANDBY FOLLOW successfully executed on all reachable sibling nodes
NOTICE: switchover was successful
DETAIL: node "base-centos-2" is now primary and node "base-centos-1" is attached as standby
NOTICE: STANDBY SWITCHOVER has completed successfully

CLUSTER STATUS

su - postgres -c 'repmgr cluster show'
ID | Name | Role | Status | Upstream | Location | Priority | Timeline | Connection string
-----+---------------+---------+-----------+---------------+----------+----------+----------+----------------------------------------------
101 | base-centos-1 | standby | running | base-centos-2 | default | 100 | 3 | host=base-centos-1 dbname=repmgr user=repmgr
102 | base-centos-2 | primary | * running | | default | 100 | 4 | host=base-centos-2 dbname=repmgr user=repmgr
103 | base-centos-3 | standby | running | base-centos-2 | default | 100 | 3 | host=base-centos-3 dbname=repmgr user=repmgr
104 | base-centos-4 | witness | * running | base-centos-2 | default | 0 | 1 | host=base-centos-4 dbname=repmgr user=repmgr

CLUSTER EVENTS FOR FAILOVER

su - postgres -c 'repmgr cluster event'
Node ID | Name | Event | OK | Timestamp | Details
---------+---------------+----------------------------+----+---------------------+-------------------------------------------------------------------------
102 | base-centos-2 | child_node_new_connect | t | 2019-07-22 20:00:04 | new witness "base-centos-4" (ID: 104) has connected
102 | base-centos-2 | child_node_new_connect | t | 2019-07-22 20:00:04 | new standby "base-centos-3" (ID: 103) has connected
104 | base-centos-4 | witness_register | t | 2019-07-22 19:59:59 | witness registration succeeded; upstream node ID is 102
103 | base-centos-3 | standby_follow | t | 2019-07-22 19:59:59 | standby attached to upstream node "base-centos-2" (ID: 102)
104 | base-centos-4 | repmgrd_upstream_reconnect | t | 2019-07-22 19:59:59 | witness monitoring connection to primary node "base-centos-2" (ID: 102)
102 | base-centos-2 | repmgrd_reload | t | 2019-07-22 19:59:58 | monitoring cluster primary "base-centos-2" (ID: 102)
101 | base-centos-1 | repmgrd_standby_reconnect | t | 2019-07-22 19:59:58 | node has become a standby, monitoring connection to upstream node 102
102 | base-centos-2 | standby_switchover | t | 2019-07-22 19:59:54 | node 102 promoted to primary, node 101 demoted to standby
101 | base-centos-1 | node_rejoin | t | 2019-07-22 19:59:54 | node 101 is now attached to node 102
102 | base-centos-2 | standby_promote | t | 2019-07-22 19:59:54 | server "base-centos-2" (ID: 102) was successfully promoted to primary

Contoh Penggunaan JOIN dalam Perintah SQL SELECT

Contoh Penggunaan JOIN dalam Perintah SQL SELECT

[Belajar SQL](http://pojokprogrammer.net/tags/belajar-sql) | Setelah sebelumnya kita anatomi dan penggunaan perintah SQL SELECT untuk melakukan proses temu kembali data (*data retrieval*), maka langkah selanjutnya kita harus mempertajam ***Skill SQL***, dengan memperbanyak latihan, dan salah satu cara untuk melatih *skill SQL* adalah dengan cara mencoba membantu menyelesaikan problem SQL yang dihadapi orang lain. Beberapa situs yang bisa kita kunjungi untuk melatih *skill SQL* adalah [Oracle Community Forum](https://community.oracle.com/community/database/developer-tools/sql_and_pl_sql), namun berbahasa Inggris. Sedangkan untuk yang berbahasa Indonesia bisa mengunjungi [Programmer Forum di Kaskus](https://www.kaskus.co.id/thread/000000000000000007584619/sql-yang-punya-problem-sql-kumpul-disini-gan-no-urgent-please/). Dalam artikel ini kita akan membahas beberapa kasus berkaitan dengan SQL JOIN.
(more…)

Temu Kembali Data (Data Retrieval) Menggunakan SELECT

Temu Kembali Data (Data Retrieval) Menggunakan SELECT Rule of Thumb

**Bagian ketiga dari rangkaian artikel tentang Konsep Database Relasional.** Jika pada bagian kedua (***Membuat Model Basis Data***) kita sudah membahas bagaimana menganalisa dan membuat design model basis data (*database model*) mulai dari model konsep (*conceptual model*), model logis (*logical model*), sampai menjadi model fisik (*physical model*). Maka di artikel ini kita akan fokus pada anatomi dan penggunaan perintah SQL SELECT — atau biasa disebut sebagai *query* — untuk melakukan proses temu kembali data (*data retrieval*). Kita akan menggunakan database PostgreSQL dan pgAdmin3 dalam artikel ini, namun tidak ada halangan untuk mempraktekkannya menggunakan database MySQL, karena secara umum sintaks SQL di semua RDBMS sama.

(more…)

Mengelola Data Hirarki di PostgreSQL

Pojok Programmer Mengelola Data Hirarki di PostgreSQL

[**Database**](http://pojokprogrammer.net/tags/database) [**Tips**](http://pojokprogrammer.net/tags/tips-dan-trik) | Semua programmer yang berkonsentrasi di pengembangan aplikasi perkantoran (*office application*) pasti pernah berhadapan dengan data hirarki (***hierarchical data***) ke dalam database relasional (*relational database*). Jika sebelumnya kita mempelajari cara mengelola data [**hirarki di MySQL**](http://pojokprogrammer.net/content/mengelola-data-hirarki-di-mysql), maka pada artikel inimkita akan mempelajarinya di [PostgreSQL](http://pojokprogrammer.net/tags/postgresql). RDBMS sendiri tidak diperuntukkan untuk menyimpan data hirarki karena relasi antar table bukanlah relasi hirarki. Namun di PostgreSQL kita bisa memanfaatkan fitur ***Common Table Expression (CTE)*** untuk melakukan reksi di SQL Query. Data hirarki yang umumnya terdapat dalam aplikasi perkantoran, seperti data departemen, cabang, kategori barang, chart of accounts, dan sebagainya. Dalam artikel ini kita akan mencoba menggunakan data ***Chart of Accounts*** atau ***Perkiraan*** yang sangat diperlukan dalam sebuah aplikasi akuntansi (*Accounting System*)

(more…)

Membuat Model Basis Data

Konsep Database Relational dan Dasar-dasar SQL - Membuat Model Basis Data

Sebelum kita membuat desain sebuah database, maka pertama-tama yang perlu kita lakukan ***requirements gathering***, yaitu mengumpulkan semua kebutuhan dan keinginan para pengguna (*users*) kemudian melakukan analisa terhadap kebutuhan dan keinginan tersebut. Salah satu *kebutuhan* yang harus didapatkan dalam proses analisa ini adalah *data apa saja* yang ingin disimpan ke dalam database. Seorang analis sistem (*system analyst*) kemudian harus dapat mengidetifikasikan apa saja entitas (*entity*) yang terlibat dalam sistem yang ingin dikembangkan, kemudian membuat ***Conceptual Model*** berupa relasi antar entitas tersebut. Dan juga pembuatan diagram konseptual (*conceptual model*) menjadi diagram logikal (*logical model*), dan akhirnya menjadi diagram fisik (*physical model*).

**Bagian kedua dari rangkaian artikel tentang [konsep database relational dan dasar-dasar SQL](http://pojokprogrammer.net/content/konsep-database-relasional-dan-bahasa-sql)**

(more…)

Atur Data Anda

Konsep Database Relasional dan Bahasa SQL Atur Data Anda

[**Belajar SQL**](http://pojokprogrammer.net/tags/belajar-sql) | Dalam database relasional, ada dua hal yang perlu kita pahami, yaitu **normalisasi** data, dan **relasi antar tabel**. Tujuan dari normalisasi adalah menghilangkan rudundansi dan duplikasi data, sehingga kita bisa menghidari kemungkinan masalah saat proses manipulasi data. Aturan-aturan yang harus diikuti pada saat kita mendisain sebuah database agar kita bisa terhindar dari duplikasi data inilah yang disebut normalisasi.

**Bagian pertama dari rangkaian artikel tentang [konsep database relational dan dasar-dasar SQL](http://pojokprogrammer.net/content/konsep-database-relasional-dan-bahasa-sql)**

(more…)

Konsep Database Relasional dan Bahasa SQL

Konsep Database Relasional dan Bahasa SQL

[**Belajar SQL**](http://pojokprogrammer.net/tags/belajar-sql) | Seiring dengan kebutuhan aplikasi-aplikasi bisnis dan layanan publik masa kini, maka kebutuhan penyimpanan data dalam bentuk database relasional menjadi hal yang tidak terelakkan, sehingga pemahaman yang baik tentang ***structured query language*** (***SQL***) menjadi hal yang **amat sangat penting** karena SQL adalah bahasa yang kita gunakan untuk *berkomunikasi* dengan database. Sayangnya kenyataan berkata lain, peningkatan skill SQL menjadi prioritas paling akhir karena kebanyakan programmer terlalu fokus pada hal lain yang lebih *seksi*, seperti, meningkatkan skill bahasa pemrograman tertentu, membuat user interface yang atraktif dan user-friendly, dan membuat aplikasi yang bisa berjalan di database apapun

(more…)

Belajar SQL: Perhitungan Stok dan Harga Rata-Rata Barang untuk Aplikasi Inventory

Perhitungan Stok dan Harga Rata-Rata Barang untuk Aplikasi Inventory Satu Perintah Single SQL Statement

Belajar SQL | Semua programmer atau software developer aplikasi perkantoran pasti pernah menggunakan SQL. minimal untuk proses CRUD (Create, Read, Update, Delete) sederhana. Namun hanya sedikit yang memanfaatkan SQL untuk proses perhitungan rumit seperti penggajian, perhitungan average harga barang, status inventory, dan sebagainya hanya dengan menggunakan Single SQL Statement, alias dengan satu perintah SQL saja. Beberapa waktu lalu saat diskusi online dengan kawan-kawan di Facebook, saya tergelitik untuk membuat contoh SQL beraksi untuk menghitung nilai stok dan harga rata-rata barang setelah terjadi proses keluar masuk barang pada bulan tertentu. Contoh kasusnya adalah seperti ini…..

Di Ketahui ;

QOB := 1.670,28 Kg ( Opening Balance )
vOB := Rp. 8.397.476,7 ( Value Opening Balance 

Akumulasi selama 1 bulan dari pembelian ( Harus Proses seluruh transaksi masuk )
SQB := 1.600,Kg ( Supply quantity Barang )
vSQB := Rp. 8.825.600 ( Value Quantity Barang )

Akumulasi pemakain material selam 1 bulan ( Harus Proses seluruh Transaksi Keluar )
uQB := 973,83 ( Used Quantity Balance )

Di Tanya ;

vuQB : …..?? ( Value used Quatity Balance )
SAB : …… ( Saldo akhir balance )
vSAB : ……..( Value saldo akhir balance )

Jawab ;

Average Bahan Baku := (( vSQB+vOB) / (QOB+SQB) )
vuQB := uQB * average bahan baku 
SAB := (( QOB+SQB)-UQB)
vSAB := SAB * average bahan baku

Ketika kita berinteraksi dengan database, maka ada Golden Rule yang harus selalu dipegang. Golden Rule ini disampaikan oleh Tom Kyte, Evangelist Oracle, dalam salah satu artikel di blog-nya

  1. Sebisa mungkin, selalu gunakan satu perintah SQL.
  2. Jika tidak bisa dalam satu perintah SQL, maka gunakan Stored Procedure
  3. Jika tidak bisa menggunakan Stored Procedure, lakukan di aplikasi kita
  4. Jika masih tidak bisa juga, mungkin kita perlu memikirkan masak-masak, sebenarnya perlu gak sih kita lakukan hal ini? 🙂

Untuk itu saya akan coba menyelesaikan problem di atas hanya menggunakan satu perintah SQL saja. Namun saya akan jelaskan langkah-demi-langkah dalam membangun "satu perintah SQL" tersebut, agar teman-teman bisa memahami algoritma dan logika yang ada di dalamnya. Untuk kasus ini saya akan menggunakan PostgreSQL untuk menyelesaikannya, namun hasil akhir adalah perintah SQL standar yang bisa digunakan di RDBMS lain. Mari kita mulai dengan membuka command line PostgreSQL ………………..

C:\Program Files\PostgreSQL\9.3\bin>psql -U postgres -d inventory
psql (9.3.1)
WARNING: Console code page (850) differs from Windows code page (1252)
         8-bit characters might not work correctly. See psql reference
         page "Notes for Windows users" for details.
Type "help" for help.

Untuk menyelesaikan kasus di atas kita memerlukan beberapa buah table, yaitu

  • table items untuk menampung data master barang.
  • table item_balances untuk menampung data saldo awal bulanan tiap-tiap barang.
  • table transactions untuk mencatat transaksi harian, keluar dan masuk barang.

Pertama-tama kita buat struktur table items.

inventory=# -- -----------------------
inventory=# -- buat table items
inventory=# -- -----------------------
inventory=# create table items (
inventory(#   id serial primary key,
inventory(#   code varchar(20) not null,
inventory(#   name varchar(200) null
inventory(# ) ;
CREATE TABLE

Kemudian isikan beberapa data menggunakan perintah insert into. dan tampilkan hasilnya untuk memastikan.

inventory=# -- -----------------------
inventory=# -- masukkan beberapa data
inventory=# -- -----------------------
inventory=# insert into items (code, name)
inventory-# select 'ITEM01', 'Barang Pertama' union all
inventory-# select 'ITEM02', 'Barang Kedua' union all
inventory-# select 'ITEM03', 'Barang Ketiga' 
inventory-# ;
INSERT 0 3

inventory=# -- -----------------------
inventory=# -- lihat hasilnya
inventory=# -- -----------------------
inventory=# select * from items 
inventory-# ;

 id |  code  |      name
----+--------+----------------
  1 | ITEM01 | Barang Pertama
  2 | ITEM02 | Barang Kedua
  3 | ITEM03 | Barang Ketiga
(3 rows)

Berikutnya kita siapkan table item_balances yang digunakan untuk menyimpan saldo awal dan harga barang setiap bulannya. untuk itu perlu field periode untuk identifikasi bulan.

inventory=# -- -----------------------
inventory=# -- buat table balance
inventory=# -- -----------------------
inventory=# create table item_balances (
inventory(#   id serial primary key,
inventory(#   item_id integer not null,
inventory(#   period date not null,
inventory(#   quantity numeric(15,2),
inventory(#   unit_price numeric(15,2),
inventory(#   foreign key (item_id) references items(id)
inventory(# ) ;
CREATE TABLE

Kemudian isikan beberapa data menggunakan perintah insert into. dan tampilkan hasilnya untuk memastikan.

inventory=# -- -----------------------
inventory=# -- masukkan beberapa data
inventory=# -- -----------------------
inventory=# insert into item_balances (item_id, period, quantity, unit_price)
inventory-# select 1, to_date('2013-12-01','YYYY-MM-DD'), 1600, 23125 union all
inventory-# select 2, to_date('2013-12-01','YYYY-MM-DD'), 1250, 13100 union all
inventory-# select 3, to_date('2013-12-01','YYYY-MM-DD'), 3250, 3100 
inventory-# ;
INSERT 0 3

inventory=# -- -----------------------
inventory=# -- lihat hasilnya
inventory=# -- -----------------------
inventory=# select * from item_balances 
inventory-# ;

 id | item_id |   period   | quantity | unit_price
----+---------+------------+----------+------------
  1 |       1 | 2013-12-01 |  1600.00 |   23125.00
  2 |       2 | 2013-12-01 |  1250.00 |   13100.00
  3 |       3 | 2013-12-01 |  3250.00 |    3100.00
(3 rows)

Karena desain database kita sudah mengikuti kaidah normalisasi, maka yang tersimpan di table item_balances hanya id barang saja, Untuk mengetahui nama barang bersangkutan, kita gunakan klausa JOIN seperti di bawah ini…

inventory=# -- -----------------------
inventory=# -- tambahkan klausa JOIN
inventory=# -- -----------------------
inventory=# select a.*, b.period, b.quantity, b.unit_price
inventory-# , b.quantity*b.unit_price as item_value
inventory-# from items a join item_balances b on a.id=b.item_id 
inventory-# ;

 id |  code  |      name      |   period   | quantity | unit_price |  item_value
----+--------+----------------+------------+----------+------------+---------------
  1 | ITEM01 | Barang Pertama | 2013-12-01 |  1600.00 |   23125.00 | 37000000.00
  2 | ITEM02 | Barang Kedua   | 2013-12-01 |  1250.00 |   13100.00 | 16375000.00
  3 | ITEM03 | Barang Ketiga  | 2013-12-01 |  3250.00 |    3100.00 | 10075000.00
(3 rows)

Sekarang buatkan table transactions untuk menampung data transaksi harian, meliputi transaksi masuk dan keluar. Untuk membedakan transaksi masuk atau keluar, kita sediakan satu field tx_type untuk membedakannya.

inventory=# -- -----------------------
inventory=# -- buat table transaksi
inventory=# -- -----------------------
inventory=# create table transactions (
inventory(#   id serial primary key,
inventory(#   item_id integer not null,
inventory(#   tx_date date not null,
inventory(#   tx_type varchar(10) not null,
inventory(#   quantity numeric(15,2),
inventory(#   unit_price numeric(15,2),
inventory(#   foreign key (item_id) references items(id)
inventory(# ) ;
CREATE TABLE

Kemudian isikan beberapa data menggunakan perintah insert into. dan tampilkan hasilnya untuk memastikan. Kita tambahkan klausa JOIN untuk mengetahui informasi lebih jelas tentang barang yang terlibat dalam transaksi yang kita masukkan

inventory=# -- -----------------------
inventory=# -- masukkan beberapa data
inventory=# -- -----------------------
inventory=# insert into transactions (item_id, tx_date, tx_type, quantity, unit_price)
inventory-# select 1, to_date('2013-12-10','YYYY-MM-DD'), 'IN', 100, 24125 union all
inventory-# select 1, to_date('2013-12-20','YYYY-MM-DD'), 'IN', 200, 25125 union all
inventory-# select 1, to_date('2013-12-05','YYYY-MM-DD'), 'OUT', 100, 0 union all
inventory-# select 1, to_date('2013-12-08','YYYY-MM-DD'), 'OUT', 200, 0 union all
inventory-# select 1, to_date('2013-12-18','YYYY-MM-DD'), 'OUT', 400, 0 union all
inventory-# select 2, to_date('2013-12-02','YYYY-MM-DD'), 'IN', 1000, 15125 union all
inventory-# select 2, to_date('2013-12-23','YYYY-MM-DD'), 'IN', 2000, 14100 union all
inventory-# select 2, to_date('2013-12-04','YYYY-MM-DD'), 'OUT', 1100, 0 union all
inventory-# select 2, to_date('2013-12-05','YYYY-MM-DD'), 'OUT', 1200, 0 union all
inventory-# select 2, to_date('2013-12-15','YYYY-MM-DD'), 'OUT', 400, 0 union all
inventory-# select 3, to_date('2013-12-12','YYYY-MM-DD'), 'IN', 1000, 3500 union all
inventory-# select 3, to_date('2013-12-24','YYYY-MM-DD'), 'IN', 2000, 3700 union all
inventory-# select 3, to_date('2013-12-04','YYYY-MM-DD'), 'OUT', 1100, 0 union all
inventory-# select 3, to_date('2013-12-15','YYYY-MM-DD'), 'OUT', 1200, 0 union all
inventory-# select 3, to_date('2013-12-25','YYYY-MM-DD'), 'OUT', 2400, 0 
inventory-# ;
INSERT 0 15

inventory=# -- -----------------------
inventory=# -- lihat hasilnya, pakai JOIN
inventory=# -- -----------------------
inventory=# select a.*, b.code, b.name
inventory-# from transactions a
inventory-# join items b on a.item_id=b.id
inventory-# order by tx_date, tx_type 
inventory-# ;

 id | item_id |  tx_date   | tx_type | quantity | unit_price |  code  |      name
----+---------+------------+---------+----------+------------+--------+----------------
  6 |       2 | 2013-12-02 | IN      |  1000.00 |   15125.00 | ITEM02 | Barang Kedua
  8 |       2 | 2013-12-04 | OUT     |  1100.00 |       0.00 | ITEM02 | Barang Kedua
 13 |       3 | 2013-12-04 | OUT     |  1100.00 |       0.00 | ITEM03 | Barang Ketiga
  3 |       1 | 2013-12-05 | OUT     |   100.00 |       0.00 | ITEM01 | Barang Pertama
  9 |       2 | 2013-12-05 | OUT     |  1200.00 |       0.00 | ITEM02 | Barang Kedua
  4 |       1 | 2013-12-08 | OUT     |   200.00 |       0.00 | ITEM01 | Barang Pertama
  1 |       1 | 2013-12-10 | IN      |   100.00 |   24125.00 | ITEM01 | Barang Pertama
 11 |       3 | 2013-12-12 | IN      |  1000.00 |    3500.00 | ITEM03 | Barang Ketiga
 14 |       3 | 2013-12-15 | OUT     |  1200.00 |       0.00 | ITEM03 | Barang Ketiga
 10 |       2 | 2013-12-15 | OUT     |   400.00 |       0.00 | ITEM02 | Barang Kedua
  5 |       1 | 2013-12-18 | OUT     |   400.00 |       0.00 | ITEM01 | Barang Pertama
  2 |       1 | 2013-12-20 | IN      |   200.00 |   25125.00 | ITEM01 | Barang Pertama
  7 |       2 | 2013-12-23 | IN      |  2000.00 |   14100.00 | ITEM02 | Barang Kedua
 12 |       3 | 2013-12-24 | IN      |  2000.00 |    3700.00 | ITEM03 | Barang Ketiga
 15 |       3 | 2013-12-25 | OUT     |  2400.00 |       0.00 | ITEM03 | Barang Ketiga
(15 rows)

Langkah berikutnya adalah membuat query untuk emnghitung summary transaksi. Kita akan menggunakan teknik crosstab untuk memindahkan row transaksi IN dan OUT menjadi kolom.

inventory=# -- -----------------------
inventory=# -- crosstab data transaksi
inventory=# -- -----------------------
inventory=# select item_id
inventory-# , date_trunc('month',tx_date) tx_date
inventory-# , sum(case when tx_type='IN' then quantity else 0 end) qty_in
inventory-# , sum(case when tx_type='IN' then quantity*unit_price else 0 end) value_in
inventory-# , sum(case when tx_type='OUT' then quantity else 0 end) qty_out
inventory-# from transactions
inventory-# group by item_id
inventory-# , date_trunc('month',tx_date) ;

 item_id |        tx_date         | qty_in  |   value_in    | qty_out
---------+------------------------+---------+---------------+---------
       2 | 2013-12-01 00:00:00+07 | 3000.00 | 43325000.0000 | 2700.00
       1 | 2013-12-01 00:00:00+07 |  300.00 |  7437500.0000 |  700.00
       3 | 2013-12-01 00:00:00+07 | 3000.00 | 10900000.0000 | 4700.00
(3 rows)

Terakhir…… lakukan JOIN terhadal table items, item_balance dan hasil crosstab transactions untuk proses perhitungan sesuai dengan rumus yang diberikan di bagian awal artikel ini.

inventory=# -- -----------------------
inventory=# -- perhitungan akhir
inventory=# -- -----------------------
inventory=# select it.id, it.code, it.name
inventory-# , bl.period, bl.quantity QOB, bl.unit_price Avg1
inventory-# , ( bl.quantity *bl.unit_price ) vOB
inventory-# , tx.qty_in SQB, tx.value_in vSQB, tx.qty_out uQB
inventory-# , ( (tx.value_in +bl.quantity *bl.unit_price) / (bl.quantity +tx.qty_in) ) Avg2
inventory-# , tx.qty_out
inventory-# * ( (tx.value_in+bl.quantity*bl.unit_price) / (bl.quantity +tx.qty_in) ) vuQB
inventory-# , ( bl.quantity +tx.qty_in ) -tx.qty_out SAB
inventory-# , ( (bl.quantity +tx.qty_in) -tx.qty_out )
inventory-# * ( (tx.value_in +bl.quantity *bl.unit_price) / (bl.quantity +tx.qty_in) ) vSAB
inventory-# from items it
inventory-# join item_balances bl on it.id = bl.item_id
inventory-# left join (
inventory(#   select item_id
inventory(#   , date_trunc('month',tx_date) tx_date
inventory(#   , sum(case when tx_type='IN' then quantity else 0 end) qty_in
inventory(#   , sum(case when tx_type='IN' then quantity*unit_price else 0 end) value_in
inventory(#   , sum(case when tx_type='OUT' then quantity else 0 end) qty_out
inventory(#   from transactions
inventory(#   group by item_id
inventory(#   , date_trunc('month',tx_date)
inventory(# ) tx
inventory-# on bl.item_id = tx.item_id
inventory-# and bl.period = tx.tx_date
inventory-# ;

 id |  code  |      name      |   period   |   qob   |   avg1   |     vob     |   sqb   |     vsqb    |   uqb   |   avg2   |    vuqb     |   sab   |   vsab
----+--------+----------------+------------+---------+----------+-------------+---------+-------------+---------+----------+-------------+---------+-------------
  1 | ITEM01 | Barang Pertama | 2013-12-01 | 1600.00 | 23125.00 | 37000000.00 |  300.00 |  7437500.00 |  700.00 | 23388.16 | 16371712.00 | 1200.00 | 28065792.00
  2 | ITEM02 | Barang Kedua   | 2013-12-01 | 1250.00 | 13100.00 | 16375000.00 | 3000.00 | 43325000.00 | 2700.00 | 14047.06 | 37927062.00 | 1550.00 | 21772943.00
  3 | ITEM03 | Barang Ketiga  | 2013-12-01 | 3250.00 |  3100.00 | 10075000.00 | 3000.00 | 10900000.00 | 4700.00 |  3356.00 | 15773200.00 | 1550.00 |  5201800.00
(3 rows)

BERHASIL!!!!!!

Kita berhasil membuat satu perintah SQL untuk perhitungan status stok akhir bulan dan menghitung harga rata-ratanya. hasil dari perintah SQL ini sudah matang, sehingga aplikasi kita – PHP atau VB.net atau lainnya – bisa langsung menampilkan hasilnya tanpa perlu melakukan proses perhitungan lagi……..

Semoga berguna….

^_^

 

Belajar SQL: Penggunaan SUM dan JOIN untuk membuat Laporan Summary

Belajar SQL: Penggunaan SUM dan JOIN untuk membuat Laporan Summary

Belajar SQL | Membuat laporan summary data dari table di database bisa dibilang gampang-gampang susah. Gampang ketika hanya membuat summary dengan sumber data satu buah table saja. Namun jadi makin rumit ketika summary melibatkan 2 (dua) table atau lebih. Silakan pelajari TUTORIAL berikut ini sebagai salah satu contoh kasus untuk membuat summary data dari 3 buah table terpisah.

Pertama-tama kita buat Table MASTER untuk sample data menggunakan perintah SQL CREATE TABLE … AS SELECT … kemudian tampilkan isinya untuk memastikan.

mysql> create table t_master as
    -> select 1 id, 'A' data union all
    -> select 2 id, 'B' data union all
    -> select 3 id, 'C' data ;
Query OK, 3 rows affected (0.19 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from t_master ;
+----+------+
| id | data |
+----+------+
|  1 | A    |
|  2 | B    |
|  3 | C    |
+----+------+
3 rows in set (0.00 sec)

Kemudian buat table TRANSAKSI pertama juga menggunakan perintah SQL CREATE TABLE … AS SELECT … kemudian tampilkan isinya untuk memastikan.

mysql> create table t_transaksi_1 as
    -> select 1 id, 'DOC 1' doc, 2000 value union all
    -> select 1 id, 'DOC 1' doc, 1000 value union all
    -> select 2 id, 'DOC 2' doc, 5000 value union all
    -> select 3 id, 'DOC 3' doc, 3000 value union all
    -> select 3 id, 'DOC 4' doc, 3000 value union all
    -> select 3 id, 'DOC 4' doc, 2000 value ;
Query OK, 6 rows affected (0.30 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> select * from t_transaksi_1 ;
+----+-------+-------+
| id | doc   | value |
+----+-------+-------+
|  1 | DOC 1 |  2000 |
|  1 | DOC 1 |  1000 |
|  2 | DOC 2 |  5000 |
|  3 | DOC 3 |  3000 |
|  3 | DOC 4 |  3000 |
|  3 | DOC 4 |  2000 |
+----+-------+-------+
6 rows in set (0.00 sec)

Berikutnya buat table TRANSAKSI kedua juga menggunakan perintah SQL CREATE TABLE … AS SELECT … kemudian tampilkan isinya untuk memastikan.

mysql> create table t_transaksi_2 as
    -> select 2 id, 'DOC 2' doc, 2000 value union all
    -> select 2 id, 'DOC 2' doc, 2000 value union all
    -> select 3 id, 'DOC 3' doc, 4000 value union all
    -> select 3 id, 'DOC 4' doc, 5000 value union all
    -> select 3 id, 'DOC 4' doc, 1000 value ;
Query OK, 5 rows affected (0.15 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from t_transaksi_2 ;
+----+-------+-------+
| id | doc   | value |
+----+-------+-------+
|  2 | DOC 2 |  2000 |
|  2 | DOC 2 |  2000 |
|  3 | DOC 3 |  4000 |
|  3 | DOC 4 |  5000 |
|  3 | DOC 4 |  1000 |
+----+-------+-------+
5 rows in set (0.00 sec)

Sekarang kita buatkan summary untuk table TRANSAKSI pertama, menggunakan GROUP BY dan fungsi aggregat SUM()

mysql>    select tm.id, data, doc, sum(value) value1
    ->    from t_master tm
    ->    join t_transaksi_1 tr on tm.id=tr.id
    ->    group by id, doc
    -> ;
+----+------+-------+---------+
| id | data | doc   | value11 |
+----+------+-------+---------+
|  1 | A    | DOC 1 |    3000 |
|  2 | B    | DOC 2 |    5000 |
|  3 | C    | DOC 3 |    3000 |
|  3 | C    | DOC 4 |    5000 |
+----+------+-------+---------+
4 rows in set (0.00 sec)

Sekarang kita buatkan summary untuk table TRANSAKSI kedua, menggunakan GROUP BY dan fungsi aggregat SUM().

mysql> select tm.id, data, doc, sum(value) value2
    ->    from t_master tm
    ->    join t_transaksi_2 tr on tm.id=tr.id
    ->    group by id, doc  ;
+----+------+-------+---------+
| id | data | doc   | value12 |
+----+------+-------+---------+
|  2 | B    | DOC 2 |    4000 |
|  3 | C    | DOC 3 |    4000 |
|  3 | C    | DOC 4 |    6000 |
+----+------+-------+---------+
3 rows in set (0.00 sec)

Sekarang gabungkan SUMMARY dari TRANSAKSI pertama dan kedua, menggunakan LEFT JOIN menjadi seperti ini.


mysql> select res1.id, res1.data, res1.doc
    -> , value1
    -> , value2
    -> from (
    ->    select tm.id, data, doc, sum(value) value1
    ->    from t_master tm
    ->    join t_transaksi_1 tr on tm.id=tr.id
    ->    group by id, doc
    -> ) res1
    -> left join (
    ->    select tm.id, data, doc, sum(value) value2
    ->    from t_master tm
    ->    join t_transaksi_2 tr on tm.id=tr.id
    ->    group by id, doc
    -> ) res2 on res1.id=res2.id and res1.doc=res2.doc ;
+----+------+-------+--------+--------+
| id | data | doc   | value1 | value2 |
+----+------+-------+--------+--------+
|  1 | A    | DOC 1 |   3000 |   NULL |
|  2 | B    | DOC 2 |   5000 |   4000 |
|  3 | C    | DOC 3 |   3000 |   4000 |
|  3 | C    | DOC 4 |   5000 |   6000 |
+----+------+-------+--------+--------+
4 rows in set (0.00 sec)

Namun masih ada yang sedikit aneh, karena ada nilai NULL di sana, maka kita gunakan perintah IFNULL() untuk mengunbahknya menjadi angka 0 (nol).


mysql> select res1.id, res1.data, res1.doc
    -> , ifnull(value1,0) value1
    -> , ifnull(value2,0) value2
    -> from (
    ->    select tm.id, data, doc, sum(value) value1
    ->    from t_master tm
    ->    join t_transaksi_1 tr on tm.id=tr.id
    ->    group by id, doc
    -> ) res1
    -> left join (
    ->    select tm.id, data, doc, sum(value) value2
    ->    from t_master tm
    ->    join t_transaksi_2 tr on tm.id=tr.id
    ->    group by id, doc
    -> ) res2 on res1.id=res2.id and res1.doc=res2.doc ;
+----+------+-------+--------+--------+
| id | data | doc   | value1 | value2 |
+----+------+-------+--------+--------+
|  1 | A    | DOC 1 |   3000 |      0 |
|  2 | B    | DOC 2 |   5000 |   4000 |
|  3 | C    | DOC 3 |   3000 |   4000 |
|  3 | C    | DOC 4 |   5000 |   6000 |
+----+------+-------+--------+--------+
4 rows in set (0.00 sec)


Demikian TUTORIAL ini semoga membantu memahami penggunaan SUBQUERY, JOIN, GROUP BY, dan fungsi agregate SUM.

Belajar SQL: CASE Clause – Logika Percabangan

Belajar SQL Logika Percabangan Case clause

Belajar SQL Logika Percabangan Case clauseSalah satu fungsi atau clause yang kurang begitu populer di SQL adalah CASE clause. Salah satu sebabnya adalah umunya SQL lebih dikenal hanya untuk proses SELECT, INSERT, UPDATE dan DELETE saja, dan karena CASE clause punya fungsi yang sama dengan IF di semua bahasa pemrograman yang ada, misalkan PHP, Java, dan sebagainya. Untuk itu saya akan berikan contoh sederhana penggunaan SQL untuk melakukan perhitungan dengan sedikit logika IF sederhana.

Misalkan kita mempunya contoh data seperti berikut ini

mysql> select * from penggunaan;
+----------+-------+--------+-------+-------+-------+-------+--------+
| kegiatan | senin | selasa | rabu  | kamis | jumat | sabtu | minggu |
+----------+-------+--------+-------+-------+-------+-------+--------+
| olahraga | ya    | tidak  | ya    | tidak | ya    | tidak | tidak  |
| diskusi  | tidak | ya     | tidak | tidak | tidak | ya    | tidak  |
| rapat    | ya    | tidak  | tidak | tidak | tidak | tidak | tidak  |
| ulangan  | tidak | ya     | ya    | ya    | tidak | tidak | tidak  |
+----------+-------+--------+-------+-------+-------+-------+--------+
4 rows in set (0.00 sec)

ketika kita ingin tahu berapa banyak dalam seminggu kegiatan-kegiatan tersebut di atas dilakukan atau tidak. Sebagai contoh untuk kegiatan OLAHRAGA dalam satu minggu dilakukan sebanyak 3 kali dan tidak dilakukan sebanyak 4 kali. jika kita bisa dengan mudah menggunakan PHP dan looping untuk menghitungnya menggunakan logika percabangan IF, misalkan.

if ($row['senin']=='ya')
    $dilakukan += 1;
else
    $tidak_dilakukan += 1;

Kalau menggunakan SQL caranya adalah seperti ini

mysql> select penggunaan.*
    -> , case when senin='ya' then 1 else 0 end
    -> + case when selasa='ya' then 1 else 0 end
    -> + case when rabu='ya' then 1 else 0 end
    -> + case when kamis='ya' then 1 else 0 end
    -> + case when jumat='ya' then 1 else 0 end
    -> + case when sabtu='ya' then 1 else 0 end
    -> + case when minggu='ya' then 1 else 0 end as dilakukan
    -> , case when senin='tidak' then 1 else 0 end
    -> + case when selasa='tidak' then 1 else 0 end
    -> + case when rabu='tidak' then 1 else 0 end
    -> + case when kamis='tidak' then 1 else 0 end
    -> + case when jumat='tidak' then 1 else 0 end
    -> + case when sabtu='tidak' then 1 else 0 end
    -> + case when minggu='tidak' then 1 else 0 end as tidak_dilakukan
    -> from penggunaan ;

Saat menghitung jumlah "dilakukan" kita berikan nilai 1 jika field berisikan 'ya', selain itu beli nilai 0. Dan saat menghitung jumlah "tidak_dilakukan" kita berikan nilai 1 jika field berisikan 'tidak', selain itu beli nilai 0. Setelah itu jumlahkan semua CASE clause tersebut sehingga kita dapatkan hasil seperti ini

+----------+-------+--------+-------+-------+-------+-------+--------+-----------+-----------------+
| kegiatan | senin | selasa | rabu  | kamis | jumat | sabtu | minggu | dilakukan | tidak_dilakukan |
+----------+-------+--------+-------+-------+-------+-------+--------+-----------+-----------------+
| olahraga | ya    | tidak  | ya    | tidak | ya    | tidak | tidak  |         3 |               4 |
| diskusi  | tidak | ya     | tidak | tidak | tidak | ya    | tidak  |         2 |               5 |
| rapat    | ya    | tidak  | tidak | tidak | tidak | tidak | tidak  |         1 |               6 |
| ulangan  | tidak | ya     | ya    | ya    | tidak | tidak | tidak  |         3 |               4 |
+----------+-------+--------+-------+-------+-------+-------+--------+-----------+-----------------+
4 rows in set (0.00 sec)

Tadaaaa….. Bagaimana? Luar biasa, bukan?

Dengan satu perintah SQL, kita bisa mendapatkan data yang matang dan ketika membuat program PHP-nya kita cukup menmpilkannya saja dalam bertuk <table></table> dan tidak perlu menghitung-hitung lagi. Mudah dan Cepat (y)