skip to main | skip to sidebar
Code 18
Manuel du savoir-faire à l'usage des geeks et des curieux
RSS
  • Accueil
  • Le web au Québec
  • Liens
  • Twitter
  • Facebook
  • À propos

jeudi 29 septembre 2011

Vérifier si une table existe dans Postgres

Publié par Infinite Loop, à 21 h 01 1 commentaire

Une base de données Postgres, de nombreux schémas dont la structure est calquée les uns sur les autres mais qui ne contiennent pas nécessairement les mêmes tables. C'est dans ce contexte que j'ai eu à écrire un script qui devait parcourir chaque schéma afin de vérifier la présence d'une table précise et si c'était le cas, appliquer une modification à la structure afin que les tables soient uniformes à travers les différents schémas.

Comment peut-on vérifier l'existance d'une table dans un schéma ? Si on fait un SELECT sur une table inexistante, peut-on capturer une exception plutôt que de provoquer une erreur ? Est-ce qu'il existe une fonction système pour en faire la vérification ? Je n'en ai pas trouvé. Par contre, en cherchant un peu dans les catalogues systèmes, j'ai pu écrire une requête SQL qui permet de vérifier la présence d'une table dans un schéma spécifié (autrement, il cherchera dans le schéma public par défaut) :

SELECT *
FROM pg_class as tbl
INNER JOIN pg_namespace as schm on tbl.relnamespace = schm.oid
WHERE schm.nspname = '' || p_s_schema || ''
AND tbl.relname = '' || p_s_tablename || ''
Si la requête retourne un enregistrement, c'est qu'une table a été trouvée (à utiliser avec EXISTS ou FOUND). Pour le reste, il suffit de l'encapsuler dans une fonction à réutiliser dans vos différents projets :
CREATE OR REPLACE FUNCTION table_exists(schema_name character varying, table_name character varying)
RETURNS boolean AS
$BODY$
BEGIN
IF EXISTS(
SELECT *
FROM pg_class as tbl
INNER JOIN pg_namespace as schm on tbl.relnamespace = schm.oid
WHERE schm.nspname = '' || schema_name || ''
AND tbl.relname = '' || table_name || ''
) THEN
RETURN true;
ELSE
RETURN false;
END IF;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
Appel à la fonction :
SELECT table_exists('public', 'employees') // retourne true ou false


Tags: PostgreSQL

mardi 27 septembre 2011

Devant une bibliothèque imposante

Publié par Infinite Loop, à 20 h 31 0 commentaire

Mon plus grand regret, c'est de ne pas m'être procuré un certain livre lorsque j'ai eu la chance de le dénicher. Je l'ai tout bonnement laissé filer. Depuis, impossible de le retrouver, même après plusieurs années. Le résultat : chaque fois que j'ai l'intention de lire quelque chose, je ne prends aucun risque et je l'achète immédiatement pour l'ajouter à ma liste de lecture. Il faut dire que je lis beaucoup. Mais chaque fois que j'en termine un, quelques livres supplémentaires sont déjà apparus sur les étagères de ma bibliothèque. Trop peu de temps disponible par rapport à l'ensemble des intérêts que j'aimerais explorer au cours de ma vie. Face à la somme du savoir du monde, je me sens démunis.

Je peux passer des heures à fouiller les librairies et les boutiques de livres usagés, juste dans l'espoir de trouver la perle rare ou quelque chose qui stimule mon intellect. Entre regarder Occupation Double et me plonger dans un livre, je sais lequel représente le meilleur investissement. Investir dans ses connaissances ouvre la voie du succès.

Voilà, j'ai trop de livres et j'ai de la difficulté à me défaire de ceux que j'ai déjà lu. On ne se débarrasse pas de la connaissance lorsqu'elle est à la portée de la main. Et puis, avoir une belle bibliothèque bien garnie permet de dégager un certain prestige. Du moins, celui de donner l'impression à ses visiteurs d'être cultivé. D'où l'idée de ne pas se laisser impressionner si facilement.

À une personne qui entrerait chez lui pour la première fois, qui découvrirait son imposante bibliothèque et qui ne trouverait rien de mieux que de lui demander s'il les avait tous lus, l'auteur et professeur Umberto Eco cite 3 réponses possibles :

  1. j'en ai lu davantage
  2. ces livres-là sont seulement ceux que je dois lire la semaine prochaine. Ceux que j'ai déjà lus sont à l'université
  3. je n'ai lu aucun de ces livres. Sinon, pourquoi les garderais-je ?
Ma librairie iTunes m'indique que si j'écoutais toutes les chansons de mon répertoire sans répétition, j'en aurais pour 83 jours de suite à entendre des pièces différentes. Pourtant, j'ai tendance à réécouter souvent les mêmes albums et à ignorer les autres. Pour en revenir à ma bibliothèque et considérant que ça prend plus de temps lire un bouquin que d'écouter un album, quelle est la probabilité de lire une deuxième fois un livre que j'ai conservé si précieusement ?

La réalité, c'est que je continue à les accumuler. Juste au cas-où. Qui sait si un jour, je finirai par imiter le musicien d'avant-garde John Zorn : avoir des livres, disques et films partout, du plancher au plafond et ce dans toutes les pièces, allant même jusqu'à condamner la cuisine pour en stocker davantage. Ça doit être de toute beauté à voir! S'il vous invite chez lui pour contempler sa collection, vous aurez à vous assoir par terre puisqu'il s'est aussi défait de son mobilier...


Tags: Le coin du geek, Livres

lundi 19 septembre 2011

Les jeunes condamnés à la location perpétuelle ?

Publié par Infinite Loop, à 19 h 27 5 commentaires

J'allais écrire un commentaire en réaction à l'article Les jeunes et la propriété: location perpétuelle? de Marc Tison sur Cyberpresse et vu la longueur de ma réponse (parce que j'en ai toujours trop à dire), j'ai préféré utilisé mon blogue comme tribune. Tout d'abord, sachez que je ne suis pas un expert en la matière et que l'écriture de ce billet est motivé par le gros bon sens.

Est-ce que les jeunes sont condamnés à vivre à perpétuité en tant que locataires ? Que ce soit leur choix ou non, il semblerait que ça soit la meilleure option, du moins du point de vue financier. Une maison engendre plus de coûts ? Oui, je suis tout à fait d'accord. Ce dont je suis moins, c'est l'exagération de l'argumentaire présenté dans le texte qui dépeint un portrait alarmiste de la situation. J'ai un peu l'impression que l'auteur (du moins, sa source) exagère les chiffres pour faire valoir son point de vue.

Je rappelle la question initiale : lequel, entre un loyer de 900$ ou l'achat d'une maison de 350000$ est le plus rentable ? Avant de se poser la question, il faut reconnaître que ces chiffres ne sont pas très réalistes. On parle de jeunes ET d'achat d'une première maison. Je ne sais pas pour vous, mais de mon côté, personne dans mon entourage n'a les moyens de s'offrir une maison à 350 000$. Ce que leur calcul ne dit pas, c'est qu'avec une hypothèque à 5% amortie sur 25 ans, ça représente des paiements de plus de 1800$ par mois, sans compter les taxes municipales et scolaires!. Si l'exemple parlait d'une propriété valant entre 200 000$ et 250 000$, ce serait déjà plus crédible (hypothèque mensuelle approximative entre 1000$ et 1300$). Si on est d'accord que le prix moyen est de 100 000$ inférieur à ce qu'ils avancent, tous les chiffres qui en découlent sont biaisés. Et alors là, l'accession à la propriété redevient soudainement possible aux jeunes.

En considérant un appartement en bon état dont le loyer est de 900$ à Montréal, ce sera sans doute un 4 ou 5 et demi avec un petit balcon suffisamment grand pour y placer un barbecue et une chaise. Pour être propriétaire de ce même appartement sous forme de condo, il faudra débourser au moins 200 000$ et ça peut vite grimper à 250 ou 300 000$. Généralement, une maison de banlieue de 350 000$ compte le double ou le triple de la superficie habitable, au moins 4 chambres, 2 salles de bain, un garage, une cour, une piscine creusée, un chien Fido, un voisin qui aime un peu trop sa pelouse, etc. À Montréal, il ne faudra pas se surprendre si on voit le prix doubler. Juste pour dire que quand on paye un loyer 900$, on s'attend à en avoir pour 900$. Pourquoi ne pas comparer le coût de dépense pour le logis au pied carré ? C'est exactement comme comparer des pommes avec des bananes. Ou de comparer l'investissement de 1000$ sur un vélo versus une voiture à 20 000$. La bicyclette est un moyen de transport pratique et écologique mais le jour où vous aurez à vous rendre sous la pluie chez de la famille qui vit à 300 km de chez vous, vous opterez certainement pour le véhicule qui vous offre les meilleurs avantages.

Avec un prix ajusté à la réalité, à supposer que la mise de fond demeure de 10%, la prime d'assurance (à payer si la mise de fond est inférieure à 20% de la valeur de la maison) chute à 4500$ plutôt que 7000$. Une différence qui vous permet de faire un beau voyage ou de l'investir à votre guise.

Pour ce qui est des taxes foncières, j'ai des amis qui ont acheté sur la rive-nord dans un tout nouveau développement et leurs taxes frôlent les 4000$ annuellement pour une maison de moins de 250000$ (blâmez les infrastructures à payer!). Mes parents habitent en banlieue dans une maison du milieu des années 80, les taxes sont maintenant de 2100$. Pour ma part, avec une maison à Montréal, la somme de mes taxes municipales et scolaires ne dépassent pas 2500$ par année. Je sais que je ne paye pas cher et durant mon magasinage, la moyenne avoisinait 3000$. Les taxes sont généralement plus chères dans les nouveaux quartiers/nouvelles constructions mais elles auront tendance à baisser et se stabiliser avec le temps. Encore une lacune dans leur méthode de calcul.

Autres points à souligner :

  • Je suppose que les frais de démarrage de 17 500$ font référence aux frais de notaire, d'inspecteur, taxe de bienvenue, déménageur, peinture et quelques rénovations urgentes. Là-dessus, je vous dirais par mon expérience que les chiffres sont un peu plus réalistes mais quand même plus élevés que dans ma situation.
  • Coût annuel d'entretien : 5000$/an. Pour certains travaux, vous devrez faire appel à un spécialiste. C'est un montant réaliste si vous faites toujours affaire avec des professionnels. Pour d'autres, si vous êtes moindrement bricoleurs et débrouillards, vous sauverez au moins 50% de la facture en le faisant vous-même. Il ne vous faut que les matériaux et un peu de votre temps. Lorsque j'étais à loyer, le propriétaire n'était pas pressé à faire changer les fenêtres et la porte-patio. Ça gelait par l'intérieur durant l'hiver et il fallait augmenter le chauffage, ce qui influençait à la hausse la facture d'électricité payée par les locataires. Étant propriétaire occupant, on fait les travaux nécessaires parce qu'on vit dedans et qu'on veut éviter les inconvénients ou que ça se dégrade. Et on ne se le cachera pas, c'est une valeur ajoutée dans le cas d'une revente.
  • Frais à la revente : rien ne nous oblige à faire affaire avec un agent lorsque viendra le moment de vendre. On minimisera la commission et il nous en restera plus dans nos poches. On aime ça hein?
  • Assurance habitation : j'ai été surpris quand mon assureur m'a dit que les coûts étaient moindre pour assurer ma maison PLUS mes biens comparativement à lorsque j'assurais uniquement mes biens en appartement. La raison : on est supposément plus responsable étant propriétaire qu'en étant locataire. Comme dans l'exemple illustré (360$ en loyer vs 900$), si le prix triple pour vous assurer, changez d'assureur!
Quelles raisons poussent les jeunes à rester à loyer ?
  • des revenus qui ne permettent pas d'acheter une maison
  • une situation temporaire pour économiser de l'argent pour une mise de fond
  • un choix de vie qui permet d'avoir plus d'argent pour se payer du luxe
Dans le texte, on indique qu'un locataire qui aurait plutôt placé son argent dans des investissements aurait une valeur d'un million de dollars en banque après 30 ans. Mon oeil! C'est de la pure théorie. Et ça ne risque pas d'arriver parce que contrairement aux baby-boomers, les jeunes veulent profiter de la vie immédiatement et ne pas se priver de rien. Faire des économies pour plus tard est de moins en moins une priorité pour les plus récentes générations. Ensuite parce qu'il faut considérer que les placements fluctuent avec l'économie. Nous ne sommes pas à l'abri de pertes financières ou d'une crise économique.

Si je me fie à ce que j'ai investi en placements le temps que j'étais à loyer et que j'extrapole le tout sur 30 ans, j'aurais probablement un porte-feuille de 150 000$ (avec intérêts). Oui, il manque un zéro par rapport aux prévisions. J'ai beau gagner un bon salaire, je n'arrive pas au même résultat qu'eux dans mon calcul. Dans mon livre à moi, avec tous les scandales et problèmes financiers des dernières années, une maison est un placement plus sûr et qu'on peut bénéficier immédiatement si on vit dedans.

Un autre point qui m'agace : les couples agés fin vingtaine ou début trentaine sont exactement dans la tranche d'âge de ceux qui souhaitent fonder une famille. Donc plus de dépenses et moins de liquidités pour une hypothèque. Selon le calculateur de la capacité d'emprunt du site web de ma banque, un couple de jeunes professionnels gagnant un salaire de 45 000$ chacun, sans dette et ayant une mise de fond de 25 000$ pourrait emprunter jusqu'à 350 000$.

C'est le portrait de votre situation ? Bravo, vous avez la capacité de payer! Pour y arriver, oubliez les voyages, les repas au restaurant, troquez votre voiture pour un vélo, évitez de vous reproduire et cessez de respirer. Aucun de ces paramètres ne sont pris en compte dans votre capacité d'emprunt. Ou encore payez un montant plus réaliste en achetant une propriété qui répond à vos besoins et profitez de tout ce que la vie a de bon à vous offrir. Quand je vois un exemple aussi absurde que celui de cet article, je me demande si ce n'est pas les propriétaires qui tentent de convaincre les jeunes qu'il est plus avantageux de rester à loyer de peur de voir s'envoler leur profit facile.

Maintenant que nous avons considéré tout ça, on peut conclure par un point dont il n'est jamais question dans l'article : il vous faudra payer votre propriété pendant 25 ans alors que pour un loyer, vous aurez à le payer toute votre vie. Pour moi, c'est une évidence que ça devrait être une variable importante dans le calcul. Si vous achetez à 30 ans, vous serez libéré de votre dette à votre 55ème anniversaire. À loyer, vous serez dépendant de votre propriétaire à qui vous devrez envoyer un chèque par mois jusqu'à votre mort. Mais ça, c'est si vous vous laissez convaincre que vous êtes condamnés à perpétuité...


Tags: Lois et principes

dimanche 18 septembre 2011

Citation no. 135 sur les grands penseurs

Publié par Infinite Loop, à 08 h 32 0 commentaire

Quand quelqu'un que vous admirez et respectez semble être en réflexion profonde, il ou elle est probablement en train de penser à aller déjeuner.

- La règle du Grand


Tags: Citations

Tri conditionnel dans un énoncé SQL

Publié par Infinite Loop, à 08 h 17 0 commentaire

Si vous avez déjà présenté les résultats d'une requête SQL sous forme de tableau dont l'entête de chaque colonne permettait de trier les données par ordre ascendant ou descendant, vous avez probablement utilisé une ou l'autre de ces techniques :

  1. construire l'énoncé SQL par concaténation en lui ajoutant dynamiquement le nom du champ avant d'envoyer à l'exécution
  2. écrire un énoncé SQL qui prend en charge la vérification du tri demandé
C'est de cette deuxième technique dont je vais vous parler, d'une part parce que c'est celle que je préfère utiliser et que j'essaie généralement d'éviter l'autre. Cet exemple sera illustré en utilisant une procédure stockée de SQL Server mais ça peut fonctionner sous d'autres systèmes puisque ce n'est qu'une façon de formuler une requête SQL standard.

Le truc est d'utiliser des CASE WHEN dans la clause ORDER BY où seule une condition sera vraie à l'exécution. Répétez au besoin pour toutes combinaisons que vous souhaitez couvrir. La procédure recevra en paramètre une constante qui permettra de déterminer la colonne et le sens du tri.

Voici un exemple concret sur lequel vous pourrez vous baser :
CREATE PROCEDURE nom_de_la_procedure
@paramTri VARCHAR(50)
AS
...

-- je n'ai conservé que l'essentiel pour plus de clareté

SELECT *
FROM dbo.inventaire
ORDER BY
CASE WHEN @paramTri = 'NOM_ITEM_ASC' THEN nom_item END ASC,
CASE WHEN @paramTri = 'NOM_ITEM_DESC' THEN nom_item END DESC,
CASE WHEN @paramTri = 'PRIX_ASC' THEN prix END ASC,
CASE WHEN @paramTri = 'PRIX_DESC' THEN prix END DESC,
CASE WHEN @paramTri = 'QUANTITE_EN_STOCK_ASC' THEN quantite_stock END ASC,
CASE WHEN @paramTri = 'QUANTITE_EN_STOCK_DESC' THEN quantite_stock END DESC,
CASE WHEN @paramTri = 'QUANTITE_COMMANDE_ASC' THEN quantite_commande END ASC,
CASE WHEN @paramTri = 'QUANTITE_COMMANDE_DESC' THEN quantite_commande END DESC,
CASE WHEN @paramTri = 'EN_STOCK_DEPUIS_ASC' THEN date_stock END ASC,
CASE WHEN @paramTri = 'EN_STOCK_DEPUIS_DESC' THEN date_stock END DESC,
nom_item ASC -- tri par défaut
L'appel à la procédure :
EXEC nom_de_la_procedure 'NOM_ITEM_ASC'
En écrivant la requête de cette façon plutôt que par concaténation, vous préviendrez aussi les risques d'injection SQL puisque si le libellé reçu par le paramètre n'est pas pris en charge par le CASE WHEN, le tri par défaut sera alors utilisé.


Tags: SQL Server

mardi 13 septembre 2011

Bouton personnalisé dans un datepicker jQueryUI

Publié par Infinite Loop, à 18 h 47 0 commentaire

Remplacer un contrôle datepicker en JavaScript qui date de 2004 par celui de jQueyUI ? Oui ! N'importe quoi pour se débarrasser d'une vieille librairie dont le rendu jure avec le reste de l'application.

Dans la plupart des écrans, il était possible de choisir une date par le calendrier mais aussi de vider la valeur sélectionnée par un simple clic (ce qui avait pour effet de vider la valeur de l'input). Avec l'ancien contrôle datepicker, chaque input text accueillant une date était associé à une icône pour ouvrir le calendrier de saisie et une autre pour vider le champ. Contrairement à jQuery qui utilise des sélecteurs pour initialiser l'interface, celui-là avait du code HTML et JavaScript ajouté manuellement.

Comme j'allais le remplacer par le datepicker de jQueryUI, mon objectif était d'éviter d'avoir à ajouter du code à l'emplacement de chaque champ date (plus propre) et de voir s'il existait une option built-in à configurer. La documentation de jQuery indique qu'on peut ajouter un "button bar" qui comprend le bouton de fermeture mais contrairement au contrôle Dialog, il ne semble pas possible d'ajouter des boutons personnalisés (du moins, pas dans la version courante 1.8.16). Seul le libellé du bouton peut être modifié pour des fins de traduction.

Et pourquoi ne pas tenter de remplacer le fonctionnement du bouton pour qu'il fasse ce dont j'ai besoin ? Après tout, si on clique à l'extérieur de la zone du calendrier, il se ferme automatiquement, ce qui ne change en rien son comportement.



Pour remplacer le fonctionnement du bouton "Done" (Terminé) par "Clear" (Vider), j'y suis arrivé par un petit hack. Ça fonctionne bien pour les besoins de la cause mais je ne peux pas garantir que ce sera fonctionnel dans tous les cas ni dans les versions ultérieures. Voyez-le comme une solution temporaire pour ajouter simplement une option de configuration.

Pour commencer, référez-vous à la documentation du datepicker de jQueryUI pour l'intégrer à votre projet. Vous aurez probablement à définir des valeurs par défaut pour le contrôle et vous aurez au minimum besoin de celles-ci (remplacez $j par $ si vous n'utilisez pas l'option noConflict) :

$j.datepicker.setDefaults({
'dateFormat':'yy-mm-dd',
'buttonImageOnly': true,
'buttonImage': 'images/icon.gif',
'showButtonPanel' : true,
'showOn': 'both'
});
Si vous inspectez le DOM, vous remarquez que le datepicker s'insère dedans uniquement au premier appel (lorsque le calendrier apparaît à l'écran). Vous trouverez le HTML immédiatement avant la balise de fermeture du BODY. C'est pourquoi il faudra utiliser la fonction live() de jQuery pour que le hack fonctionne.
$j(document).ready(function(){
$j('button.ui-datepicker-close').live('click',
function(){
var id = $j('button.ui-datepicker-current').attr('onclick').replace(/.*gotoToday\('#(.*)'\);/gi, '$1');
if( $j('button.ui-datepicker-close', $j('#ui-datepicker-div')).html() == 'Clear' ){
$j('#' + id).val('');
}
}
);
});
La première chose à faire, c'est d'attacher un événement click en mode live sur le bouton qui possède la classe ui-datepicker-close. Il n'y en aura qu'un seul pour tous les champs de la page qui utilisent un datepicker. Dans le markup HTML généré sur demande pour le calendrier, le nom du champ associé (ID de l'input) se trouve passé en paramètre à une fonction attachée inline à l'élément. J'utilise une regex pour l'extraire de l'attribut. Enfin, je vérifie que le libellé du bouton est égal à "Clear" (ou toute autre valeur que vous aurez choisi selon votre personnalisation). Si c'est le cas, je vide l'input.

Certaines instances du calendrier pourront maintenant avoir ou non le changement de fonctionnalité du bouton. Pour qu'il l'ait, il suffira de changer le libellé du bouton en visant un ID en particulier ou un sélecteur CSS lors de l'initialisation.
$j(document).ready(function(){

// avec bouton close (standard)
$j('#datePicker1').datepicker();

// avec bouton Clear
$j('.datePicker2').datepicker({'closeText' : 'Clear'});

}
Vous auriez fait autrement ? Partagez votre solution!


Tags: JavaScript

dimanche 11 septembre 2011

Mon top 3 photos du 11 septembre 2011

Publié par Infinite Loop, à 22 h 22 0 commentaire

Aujourd'hui était le 10ème anniversaire des attentats du World Trade Center. Plutôt que de m'écraser devant la télévision pour regarder les reportages, j'ai décidé de sortir et profiter d'une des dernières belles journées chaudes de l'été. Direction Terrebonne pour retrouver des amis à une exposition de voitures anciennes.

Voici donc un court résumé de ma journée en photos pour voir ce que vous avez manqué.

1ère position

La fameuse Delorean d'AMC, popularisée par le film Back To The Future. Elle fait rêver les jeunes et les moins jeunes, même si elle n'a aucun convecteur temporel. Je serais curieux de savoir combien il y en a au Québec.



2ème position

On annonçait des bagnoles modifiées. Cherchez la pièce qui n'est pas d'origine.



3ème position

Pour une raison que j'ignore, il semblerait que les baby boomers apprécient un peu trop les poupées boudeuses puisqu'il y en avait partout. Mais euh, qu'est-ce qu'ils font ?



P.s. 4,15$ pour un aller simple Montréal-Terrebonne en transport en commun, c'est du vol.


Tags: Curiosités

Citation no. 134 sur le mensonge

Publié par Infinite Loop, à 08 h 26 1 commentaire

On ne ment jamais autant qu'avant les élections, pendant la guerre et après la chasse.

- Otto von Bismarck


Tags: Citations

mercredi 7 septembre 2011

Sélectionner le 1er enregistrement de chaque groupe en SQL

Publié par Infinite Loop, à 20 h 29 3 commentaires

Voici mon casse-tête SQL auquel j'ai eu à faire face hier et dont j'ai trouvé la solution plus tôt aujourd'hui. Pour vous mettre en situation, imaginez un site web où une liste de produits s'affiche en page d'accueil. Bien que chaque produit soit classé dans une catégorie, la requête initiale n'en tenait pas compte et se contentait d'afficher aléatoirement quelques produits du catalogue.

La demande que j'ai reçu consistait à modifier l'affichage de façon à choisir un produit au hasard dans chaque catégorie.

Voici une représentation simplifiée de la structure des tables ainsi que des données bidons pour que vous puissez reproduire ce tutoriel :

CREATE TABLE categories
(
category_id serial NOT NULL,
category_name character varying(50) NOT NULL
)

CREATE TABLE products
(
product_id serial NOT NULL,
category_id integer NOT NULL,
product_name character varying(100) NOT NULL,
price numeric
)

-- populer les tables avec des enregistrements

INSERT INTO categories (category_name)
VALUES
('Catégorie 1'),
('Catégorie 2'),
('Catégorie 3');

INSERT INTO products(category_id, product_name, price)
VALUES
(1, 'Produit 1', 10),
(1, 'Produit 2', 25),
(2, 'Produit 3', 5),
(2, 'Produit 4', 17),
(3, 'Produit 5', 22),
(3, 'Produit 6', 11);
J'ai mis à l'essai quelques modèles de requêtes SQL pour répondre au besoin. La première qui m'est venue en tête consistait à utiliser des sous-requêtes. À partir de la liste des catégories, lancer pour chacune une sous-requête pour obtenir un produit au hasard, avec la fonction random() :
SELECT c.category_id, c.category_name,
(SELECT product_name FROM products as p WHERE p.category_id = c.category_id ORDER BY random() LIMIT 1)
FROM categories as c
ORDER BY c.category_name
C'est bien, mais on n'obtient qu'un nom aléatoire de produit qui correspond à la catégorie et aucun autre champs de l'enregistrement. De plus, il est impossible de lancer une deuxième sous-requête en parallèle puisque les résultats ne feront pas référence au même enregistrement.
-- mauvais!!!
SELECT c.category_id, c.category_name,
(SELECT product_name FROM products as p WHERE p.category_id = c.category_id ORDER BY random() LIMIT 1),
(SELECT product_id FROM products as p WHERE p.category_id = c.category_id ORDER BY random() LIMIT 1)
FROM categories as c
ORDER BY c.category_name
Une autre possibilité aurait été d'utiliser une requête par catégorie de produits et de les combiner avec des UNION. Sauf qu'il faudrait connaître d'avance le nombre de catégories...

Essayons à l'inverse en utilisant la table de produits et une jointure sur la table des catégories.
SELECT c.category_name, p.*
FROM products as p
INNER JOIN categories as c ON c.category_id = p.category_id
ORDER BY c.category_name, random()
Ici, nous obtenons la liste de tous les produits classés par catégories, dont l'ordre des produits est généré aléatoirement à partir du tri secondaire (toujours avec "random()"). Ici encore, nous n'avons pas réussi à filtrer la liste pour ne conserver qu'un seul produit par catégorie.

L'astuce : l'instruction OVER PARTITION, valide autant sous Postgres (comme dans mon cas) que dans SQL Server.
-- solution
SELECT c.category_name, t.*
FROM (
SELECT *, MAX(random_order) OVER (PARTITION BY category_id)
FROM (
SELECT *, random() as random_order
FROM products
) as tmp
) as t
INNER JOIN categories as c ON c.category_id = t.category_id
WHERE MAX = random_order
ORDER BY c.category_id
Pour comprendre la solution, vous devez d'abord savoir qu'il est possible d'utiliser une sous-requête (ou une table dérivée) qui retourne un résultat plutôt que de spécifier le nom d'une table. Pour commencer l'analyse, jetez un oeil à la sous-requête la plus imbriquée. J'ajoute une valeur aléatoire avec random() (de type double, comprise entre 0 et 1) à chaque enregistrement de la table products que je nomme avec l'alias random_number. Il est ensuite nécessaire d'indiquer le champ utilisé pour le regroupement avec PARTITION. Dans mon cas, je veux que ça se fasse par catégorie. Remarquez qu'il n'y a pas de clause ORDER BY à ma requête puisque je prendrai tout simplement l'enregistrement ayant le numéro aléatoire le plus élevé avec MAX(random_number). La clause MAX = random_order me permettra ensuite de filtrer les résultats pour ne conserver que le premier enregistrement de chaque catégorie.

Important à noter : pour que ça fonctionne, vous devez obligatoirement donner un alias aux sous-requêtes qui servent de tables (dans mon exemple final : "tmp" et "t"). Autrement, vous obtiendrez une erreur de type SQL state: 42601.

En espérant que ça vous évitera de chercher trop longtemps comme je l'ai fait. Si vous avez d'autres solutions originales, n'hésitez pas à me les envoyer. Je serais curieux de les comparer.


Tags: PostgreSQL, SQL Server

lundi 5 septembre 2011

La réponse sera toujours 8

Publié par Infinite Loop, à 08 h 47 2 commentaires

Petit exercice mathématique :

  1. Pensez à un nombre au hasard entre 1 et 100 (vous pouvez en choisir un supérieur et je ne vous en voudrai pas si vous utilisez une calculatrice)
  2. Soustrayez 1 de ce nombre
  3. Multipliez le résultat par 3
  4. Ajoutez 12 au résultat
  5. Divisez la réponse par 3
  6. Ajoutez 5 à la réponse
  7. Soustrayez le nombre que vous aviez choisi à l'étape 1 (en espérant que vous vous en rappelez encore!)
Je devine que la réponse est 8. Vous avez un doute ? Essayez à nouveau.


Tags: Mathématique

dimanche 4 septembre 2011

Récupérer l'ID d'un enregistrement inséré sous Postgres

Publié par Infinite Loop, à 14 h 03 2 commentaires

En programmation, c'est une bonne stratégie de connaître des alternatives pour écrire du code qui fait le même travail. On se rend compte avec le temps qu'il n'y a pas de mauvaise façon de faire, juste des meilleures.

Quand j'ai écrit mes premières lignes de code pour des fonctions Postgres et que j'avais à insérer un enregistrement pour récupérer et retourner la valeur de l'identificateur unique (par sa séquence / clé primaire), j'ai pris l'habitude d'y aller en deux étapes : l'insertion d'abord, la récupération ensuite. Le tout étant encapsulé dans une fonction suivant les règles de transaction, ça fonctionnait bien.

CREATE OR REPLACE FUNCTION insert_something(fname character varying, lname character varying)

RETURNS integer AS
$BODY$
BEGIN
INSERT INTO artist (firstname, lastname)
VALUES (fname, lname);

RETURN currval('public.artist_id_artist_seq');
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
Avec ce code, il est impératif de connaître le nom de la séquence pour obtenir la valeur courante de l'insertion qui la précède. Et comme on utilise généralement le type SERIAL pour faciliter la création d'une clé primaire qui génère du même coup la séquence avec un nom générique, il faudra d'abord trouver ce nom.

L'énoncé pour obtenir le nom d'une séquence appliquée à une colonne d'une table :
SELECT pg_get_serial_sequence('artist', 'id_artist')

-- "public.t_artist_id_artist_seq"
Et on demande sa valeur par currval :
SELECT currval('public.artist_id_artist_seq') as id;


-- ou en l'assignant à une variable pl/pgsql

id := currval('public.artist_id_artist_seq');
La requête retournera l'ID généré par l'appel de la fonction :
SELECT insert_something('Jimi', 'Hendrix') // 4
Pour faire exactement la même chose, on pourrait réécrire le code en utilisant une particularité de Postgres pour demander à la requête SQL de retourner la valeur d'un champ dans le même énoncé en utilisant le mot clé RETURNING qui l'assignera à une variable déclarée.
CREATE OR REPLACE FUNCTION insert_something(fname character varying, lname character varying)

RETURNS integer AS
$BODY$
DECLARE
id integer; -- déclaration de la valeur de retour
BEGIN
INSERT INTO t_test (firstname, lastname)
VALUES (fname, lname)
RETURNING "id_record" INTO STRICT id;

RETURN id;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
En fait, vous pouvez retourner n'importe quelle valeur de l'enregistrement venant d'être créé, que ce soit la valeur de la séquence ou la valeur par défaut définie dans un champ de la table (par exemple une date de création avec comme valeur par défaut "now()"). Cependant, si la valeur à retourner est insérée par un trigger de type AFTER INSERT, sa valeur sera nulle car Postgres fait appel au trigger uniquement après l'insertion (BEFORE INSERT ne présente pas de problème).

L'énoncé qui utilise RETURNING a l'avantage qu'on n'a pas à connaître le nom de la séquence et que ça fonctionnera quand même si son nom change dans le futur (tant que le nom du champ demandé reste inchangé!).


Tags: PostgreSQL

Citation no. 133 sur l'art d'être bien informé

Publié par Infinite Loop, à 09 h 39 0 commentaire

Dans toute organisation, il y aura toujours une personne qui sait ce qui se passe; cette personne doit être virée.

- Loi de Conway


Tags: Citations

jeudi 1 septembre 2011

La survie du petit commerce

Publié par Infinite Loop, à 22 h 52 0 commentaire

J'ai appris aujourd'hui qu'un disquaire indépendant où j'ai déjà travaillé dans une autre vie allait fermer ses portes d'ici peu. Pincement au coeur. Les disques, ça ne vend plus. C'est même surprenant qu'il ait survécu aussi longtemps. Maintenant que nous sommes à l'ère numérique, les gens n'accordent plus d'importance à l'objet CD. Ni pour les livres et les DVD. La chaîne de location de films Blockbuster a fermé ses portes au Canada. Les torrents n'ont jamais été aussi populaires. Demandez à un jeune de 12 ans comment il consomme sa musique : achetée en ligne sur iTunes et déposée directement dans son lecteur mp3. Dans le cas du groupe Misteur Valaire, on coupe les intermédiaires avec une stratégie "direct-to-fan" (remarquez, ça a du bon). Il n'y a plus d'objet physique à manipuler. Ni de plaisir à admirer le livret, à s'imprégner de l'univers de l'artiste en lisant les paroles et en regardant les photos si évocatrices du contexte de création (on s'entend, ça vaut pour certains styles musicaux seulement, pas pour les artistes biodégradables ou jetables après usage).

Comme client, se rendre chez ce disquaire représentait pour moi des heures à discuter avec des passionnés. Ils connaissaient mes goûts et savaient exactement ce qui allait m'intéresser. Du service comme vous en verrez plus jamais. En discutant au téléphone avec un futur ex-employé, je lui ai avoué que ça faisait un bout de temps que je n'y étais pas retourné. Comme tout le monde, j'ai suivi le mouvement et j'ai remplacé mes habitudes de consommation en achetant sur Internet, surtout en raison des prix. Pour ce qui est des mp3, j'ai regretté mon choix et je cherche à racheter mes albums préférés en copies palpables. Ça se fait lentement et pas à n'importe quel prix. Juste pour ne pas avoir l'impression d'avoir payé le même album en double. D'ailleurs, eBay c'est une excellente place pour profiter de ceux qui se débarassent de leurs CD à bas prix après les avoir compressés en mp3.

Chez Indigo

Plus le temps passe, plus je finis par croire que certains types de magasins n'auront plus pignon sur rue. On cherche le meilleur prix, à tout prix. D'où la popularité des Walmart de ce monde. Il n'y a pas si longtemps, je suis allé chez le libraire Chapters/Indigo. J'avais consulté leur site web avant d'aller à la succursale du centre-ville pour vérifier la disponibilité d'un livre. Il restait une copie en inventaire au prix de 35$. Lorsque j'ai pris le livre sur l'étalage, j'ai vérifié au verso et l'étiquette indiquait 55$. J'ai demandé à la commis s'il s'agissait d'une erreur. Étonnamment non. J'étais prêt à passer à la caisse et contre toute attente, il n'y avait aucun moyen de faire égaler le prix. C'était pourtant la même bannière commerciale mais ils se permettaient d'offrir des prix différents en magasin et sur le web. La jeune fille finit par me recommander d'acheter en ligne pour bénéficier des meilleurs prix. Ce à quoi je répondis : tu te rends compte que tu m'encourages à te faire perdre ton boulot ? Malaise.

Ma première guitare

Un autre exemple m'est venu en tête au milieu de la nuit. Quand j'étais adolescent, j'ai magasiné ma première guitare (une Fender Stratocaster "made in Mexico") en gardant en tête que je devais respecter mon budget. Je me souviens avoir visité deux magasins qui avaient une approche complètement différente. Au premier, le jeune vendeur semblait plus préoccupé à m'impressionner par son talent de guitariste que de me transmettre de l'information pertinente. À l'autre, le vendeur (un drummer qui s'appelait Bruno) a passé 1h30 à m'expliquer la base, ce qui est important de regarder, les avantages et inconvénients, etc. Au final, le package guitare, ampli, étui, câble, accordeur, etc coûtait un peu plus cher que chez le concurrent. Mais la qualité du service a fait pencher la balance en sa faveur. Ce magasin s'appelait St-Jean Musique et était situé sur le boulevard des Laurentides à Laval. Depuis, il semble avoir fermé ses portes ou avoir changé de nom car une autre boutique du même genre occupe les locaux. Et le concurrent ? Eh bien il a pris de l'expansion et a ouvert d'autres succursales au fil des ans. Comme quoi le meilleur prix l'emporte généralement sur la qualité du service.

Le dilemme : où acheter ?

Cette semaine, j'ai magasiné pour un article plutôt dispendieux dans différents magasins de Montréal. Avec les taxes, le prix frôle les 3000$. La différence entre le prix le plus haut et le plus bas est peut-être de 40 ou 50$. Sur le total, c'est peu. Le premier vendeur était d'un magasin réputé mais il essayait de me vendre sous pression. Ceux des deux autres commerces visités ont bien répondu à mes questions. L'un était d'un magasin à grande surface avec un vendeur compétent mais à commission, l'autre un petit commerce indépendant de mon quartier avec des employés passionnés. En effectuant une recherche sur les Internets, j'ai noté que je pouvais commander le même produit en ligne dans un magasin en Ontario. En plus d'obtenir la livraison gratuite, je sauvais un peu plus de 200$ en taxes (on ne paye que la taxe sur les produits et services de 5% plutôt que la taxe combinée de presque 14%).

En tant que consommateur, j'ai le choix d'aller :

  • Chez celui qui voulait me vendre sous pression
  • Au magasin à grand surface tenu par des actionnaires
  • Dans la boutique en ligne d'un commerce situé dans une autre province (on oublie le service après-vente)
  • Au petit commerce local probablement tenu par un voisin de quartier qui risque tôt ou tard de devoir se battre pour sa survie face aux gros joueurs
Cette fois-ci, la tentation est grande de faire affaire avec le petit commerce. Après tout, ne devrait-on pas donner son argent à celui qui nous montre du respect, qui est à l'écoute de nos besoins et qui offre le meilleur service tout en ne prenant pas sa clientèle pour acquis ? Juste par principe.


Tags: Lois et principes

Messages plus récents Messages plus anciens Accueil
S'abonner à : Messages (Atom)
    Suivre @code18 sur Twitter

    Catégories

    • Apache (21)
    • Citations (167)
    • Club Vidéo (24)
    • Coffre à outils (55)
    • CSS (8)
    • Curiosités (117)
    • Design Pattern (2)
    • Drupal (8)
    • Easter Eggs (22)
    • Extensions Firefox (20)
    • GIMP (7)
    • Histoire (21)
    • HTML (32)
    • Humour (57)
    • Intégration (34)
    • iPod (12)
    • JavaScript (110)
    • Jeu de combat (6)
    • Le coin du geek (128)
    • Liens (12)
    • Linux (56)
    • Livres (78)
    • Lois et principes (46)
    • Marché des saveurs (26)
    • Mathématique (18)
    • Mobile (5)
    • Montréal (32)
    • Musique (112)
    • Pancartes et écriteaux (16)
    • Perl (8)
    • Pérou (1)
    • PHP (130)
    • PostgreSQL (44)
    • Programmation (105)
    • Saviez-vous que (55)
    • Sécurité (22)
    • SEO (5)
    • SQL Server (22)
    • Vieilles publicités (6)
    • Virtualisation (8)
    • Voyages (1)
    • Zend Framework (26)

    Divers

    Archives

    • ►  2015 (6)
      • ►  août 2015 (1)
      • ►  juillet 2015 (1)
      • ►  février 2015 (3)
      • ►  janvier 2015 (1)
    • ►  2014 (8)
      • ►  décembre 2014 (1)
      • ►  novembre 2014 (1)
      • ►  octobre 2014 (1)
      • ►  août 2014 (2)
      • ►  juillet 2014 (2)
      • ►  janvier 2014 (1)
    • ►  2013 (53)
      • ►  décembre 2013 (2)
      • ►  novembre 2013 (1)
      • ►  octobre 2013 (3)
      • ►  septembre 2013 (2)
      • ►  août 2013 (5)
      • ►  juillet 2013 (3)
      • ►  juin 2013 (5)
      • ►  mai 2013 (3)
      • ►  avril 2013 (7)
      • ►  mars 2013 (7)
      • ►  février 2013 (11)
      • ►  janvier 2013 (4)
    • ►  2012 (105)
      • ►  décembre 2012 (8)
      • ►  novembre 2012 (5)
      • ►  octobre 2012 (4)
      • ►  septembre 2012 (1)
      • ►  août 2012 (8)
      • ►  juillet 2012 (7)
      • ►  juin 2012 (7)
      • ►  mai 2012 (10)
      • ►  avril 2012 (13)
      • ►  mars 2012 (15)
      • ►  février 2012 (15)
      • ►  janvier 2012 (12)
    • ▼  2011 (146)
      • ►  décembre 2011 (14)
      • ►  novembre 2011 (11)
      • ►  octobre 2011 (12)
      • ▼  septembre 2011 (13)
        • Vérifier si une table existe dans Postgres
        • Devant une bibliothèque imposante
        • Les jeunes condamnés à la location perpétuelle ?
        • Citation no. 135 sur les grands penseurs
        • Tri conditionnel dans un énoncé SQL
        • Bouton personnalisé dans un datepicker jQueryUI
        • Mon top 3 photos du 11 septembre 2011
        • Citation no. 134 sur le mensonge
        • Sélectionner le 1er enregistrement de chaque group...
        • La réponse sera toujours 8
        • Récupérer l'ID d'un enregistrement inséré sous Pos...
        • Citation no. 133 sur l'art d'être bien informé
        • La survie du petit commerce
      • ►  août 2011 (15)
      • ►  juillet 2011 (17)
      • ►  juin 2011 (18)
      • ►  mai 2011 (15)
      • ►  avril 2011 (9)
      • ►  mars 2011 (7)
      • ►  février 2011 (3)
      • ►  janvier 2011 (12)
    • ►  2010 (398)
      • ►  décembre 2010 (29)
      • ►  novembre 2010 (28)
      • ►  octobre 2010 (32)
      • ►  septembre 2010 (34)
      • ►  août 2010 (22)
      • ►  juillet 2010 (35)
      • ►  juin 2010 (42)
      • ►  mai 2010 (36)
      • ►  avril 2010 (37)
      • ►  mars 2010 (34)
      • ►  février 2010 (32)
      • ►  janvier 2010 (37)
    • ►  2009 (429)
      • ►  décembre 2009 (32)
      • ►  novembre 2009 (34)
      • ►  octobre 2009 (33)
      • ►  septembre 2009 (37)
      • ►  août 2009 (37)
      • ►  juillet 2009 (39)
      • ►  juin 2009 (38)
      • ►  mai 2009 (37)
      • ►  avril 2009 (35)
      • ►  mars 2009 (36)
      • ►  février 2009 (32)
      • ►  janvier 2009 (39)
    • ►  2008 (84)
      • ►  décembre 2008 (34)
      • ►  novembre 2008 (39)
      • ►  octobre 2008 (11)

    Abonnés

Copyright © All Rights Reserved. Code 18 | Converted into Blogger Templates by Theme Craft