web-dev-qa-db-fra.com

Redshift. Convertir des valeurs délimitées par des virgules en lignes

Je me demande comment convertir des valeurs délimitées par des virgules en lignes dans Redshift. J'ai peur que ma propre solution ne soit pas optimale. S'il vous plaît donnez votre avis. J'ai une table avec l'une des colonnes avec des valeurs séparées par un coma. Par exemple:

J'ai:

user_id|user_name|user_action
-----------------------------
1      | Shone   | start,stop,cancell...

J'aimerais voir

user_id|user_name|parsed_action 
------------------------------- 
1      | Shone   | start        
1      | Shone   | stop         
1      | Shone   | cancell      
....
29
Yuri Levinsky

Une légère amélioration par rapport à la réponse existante consiste à utiliser une deuxième table "nombres" qui énumère toutes les longueurs de liste possibles, puis à utiliser un cross join pour rendre la requête plus compacte.

Redshift n'a pas de méthode simple pour créer une table de nombres que je connaisse, mais nous pouvons utiliser un peu de piratage de https://www.periscope.io/blog/generate-series-in- redshift-and-mysql.html pour en créer un en utilisant des numéros de ligne.

Plus précisément, si nous supposons le nombre de lignes dans cmd_logs est supérieur au nombre maximal de virgules dans user_action colonne, nous pouvons créer une table de nombres en comptant les lignes. Pour commencer, supposons qu'il y ait au plus 99 virgules dans le user_action colonne:

select 
  (row_number() over (order by true))::int as n
into numbers
from cmd_logs
limit 100;

Si nous voulons être fantaisistes, nous pouvons calculer le nombre de virgules à partir de cmd_logs table pour créer un ensemble de lignes plus précis dans numbers:

select
  n::int
into numbers
from
  (select 
      row_number() over (order by true) as n
   from cmd_logs)
cross join
  (select 
      max(regexp_count(user_action, '[,]')) as max_num 
   from cmd_logs)
where
  n <= max_num + 1;

Une fois qu'il y a une table numbers, on peut faire:

select
  user_id, 
  user_name, 
  split_part(user_action,',',n) as parsed_action 
from
  cmd_logs
cross join
  numbers
where
  split_part(user_action,',',n) is not null
  and split_part(user_action,',',n) != '';
26
Bob Baxley

Vous pouvez obtenir le résultat attendu avec la requête suivante. J'utilise "UNION ALL" pour convertir une colonne en ligne.

select user_id, user_name, split_part(user_action,',',1) as parsed_action from cmd_logs
union all
select user_id, user_name, split_part(user_action,',',2) as parsed_action from cmd_logs
union all
select user_id, user_name, split_part(user_action,',',3) as parsed_action from cmd_logs
2
Masashi Miyazaki

Une autre idée consiste à transformer d'abord votre chaîne CSV en JSON, puis à extraire JSON, comme suit:

... '["' || replace( user_action, '.', '", "' ) || '"]' AS replaced

... JSON_EXTRACT_ARRAY_ELEMENT_TEXT(replaced, numbers.i) AS parsed_action

Où "nombres" est le tableau de la première réponse. L'avantage de cette approche est la possibilité d'utiliser la fonctionnalité JSON intégrée.

1
YakovK

Tard dans la soirée mais j'ai eu quelque chose qui fonctionnait (quoique très lent cependant)

with nums as (select n::int n
from
  (select 
      row_number() over (order by true) as n
   from table_with_enough_rows_to_cover_range)
cross join
  (select 
      max(json_array_length(json_column)) as max_num 
   from table_with_json_column )
where
  n <= max_num + 1)
select *, json_extract_array_element_text(json_column,nums.n-1) parsed_json
from  nums, table_with_json_column
where json_extract_array_element_text(json_column,nums.n-1) != ''
and nums.n <= json_array_length(json_column) 

Merci à réponse de Bob Baxley pour l'inspiration

0
aarbor

Juste amélioration pour la réponse ci-dessus https://stackoverflow.com/a/31998832/1265306

Génère une table de nombres en utilisant le SQL suivant https://discourse.looker.com/t/generating-a-numbers-table-in-mysql-and-redshift/482

SELECT 
  p0.n 
  + p1.n*2 
  + p2.n * POWER(2,2) 
  + p3.n * POWER(2,3)
  + p4.n * POWER(2,4)
  + p5.n * POWER(2,5)
  + p6.n * POWER(2,6)
  + p7.n * POWER(2,7) 
  as number  
INTO numbers
FROM  
  (SELECT 0 as n UNION SELECT 1) p0,  
  (SELECT 0 as n UNION SELECT 1) p1,  
  (SELECT 0 as n UNION SELECT 1) p2, 
  (SELECT 0 as n UNION SELECT 1) p3,
  (SELECT 0 as n UNION SELECT 1) p4,
  (SELECT 0 as n UNION SELECT 1) p5,
  (SELECT 0 as n UNION SELECT 1) p6,
  (SELECT 0 as n UNION SELECT 1) p7
ORDER BY 1
LIMIT 100

"ORDER BY" est là uniquement si vous voulez le coller sans la clause INTO et voir les résultats

0
naviram

Voici ma réponse tout aussi terrible.

J'ai une table users, puis une table events avec une colonne qui n'est qu'une chaîne d'utilisateurs délimitée par des virgules lors dudit événement. par exemple

event_id | user_ids
1        | 5,18,25,99,105

Dans ce cas, j'ai utilisé les fonctions LIKE et les caractères génériques pour créer une nouvelle table qui représente chaque Edge utilisateur de l'événement.

SELECT e.event_id, u.id as user_id
FROM events e
LEFT JOIN users u ON e.user_ids like '%' || u.id || '%'

Ce n'est pas joli, mais je le jette dans une clause WITH pour ne pas avoir à l'exécuter plus d'une fois par requête. Je vais probablement construire un ETL pour créer cette table tous les soirs de toute façon.

En outre, cela ne fonctionne que si vous avez une deuxième table qui ne a une ligne par possibilité unique. Si ce n'est pas le cas, vous pouvez faire LISTAGG pour obtenir une seule cellule avec toutes vos valeurs, l'exporter vers un CSV et re-upload that comme un tableau pour vous aider.

Comme je l'ai dit: une terrible et mauvaise solution.

0
ScottieB